* Revision::setUserIdAndName() was deprecated.
* Access to TitleValue class properties was deprecated, the relevant getters
should be used instead.
+* DifferenceEngine::getDiffBodyCacheKey() is deprecated. Subclasses should
+ override DifferenceEngine::getDiffBodyCacheKeyParams() instead.
+* The deprecated MW_DIFF_VERSION constant was removed.
+ DifferenceEngine::MW_DIFF_VERSION should be used instead.
+* Use of Maintenance::error( $err, $die ) to exit script was deprecated. Use
+ Maintenance::fatalError() instead.
== Compatibility ==
MediaWiki 1.31 requires PHP 5.5.9 or later. There is experimental support for
* @param string $heading (optional)
* @return string of HTML representing a box.
*/
- public static function messageBox( $html, $className, $heading = '' ) {
+ private static function messageBox( $html, $className, $heading = '' ) {
if ( $heading ) {
$html = self::element( 'h2', [], $heading ) . $html;
}
*/
const PARAM_ISMULTI_LIMIT2 = 22;
+ /**
+ * (integer) Maximum length of a string in bytes (in UTF-8 encoding).
+ * @since 1.31
+ */
+ const PARAM_MAX_BYTES = 23;
+
+ /**
+ * (integer) Maximum length of a string in characters (unicode codepoints).
+ * @since 1.31
+ */
+ const PARAM_MAX_CHARS = 24;
+
/**@}*/
const ALL_DEFAULT_STRING = '*';
);
}
- // More validation only when choices were not given
- // choices were validated in parseMultiValue()
if ( isset( $value ) ) {
+ // More validation only when choices were not given
+ // choices were validated in parseMultiValue()
if ( !is_array( $type ) ) {
switch ( $type ) {
case 'NULL': // nothing to do
$value = array_unique( $value );
}
+ if ( in_array( $type, [ 'NULL', 'string', 'text', 'password' ], true ) ) {
+ foreach ( (array)$value as $val ) {
+ if ( isset( $paramSettings[self::PARAM_MAX_BYTES] )
+ && strlen( $val ) > $paramSettings[self::PARAM_MAX_BYTES]
+ ) {
+ $this->dieWithError( [ 'apierror-maxbytes', $encParamName,
+ $paramSettings[self::PARAM_MAX_BYTES] ] );
+ }
+ if ( isset( $paramSettings[self::PARAM_MAX_CHARS] )
+ && mb_strlen( $val, 'UTF-8' ) > $paramSettings[self::PARAM_MAX_CHARS]
+ ) {
+ $this->dieWithError( [ 'apierror-maxchars', $encParamName,
+ $paramSettings[self::PARAM_MAX_CHARS] ] );
+ }
+ }
+ }
+
// Set a warning if a deprecated parameter has been passed
if ( $deprecated && $value !== false ) {
$feature = $encParamName;
}
}
+ if ( isset( $settings[self::PARAM_MAX_BYTES] ) ) {
+ $info[] = $context->msg( 'api-help-param-maxbytes' )
+ ->numParams( $settings[self::PARAM_MAX_BYTES] );
+ }
+ if ( isset( $settings[self::PARAM_MAX_CHARS] ) ) {
+ $info[] = $context->msg( 'api-help-param-maxchars' )
+ ->numParams( $settings[self::PARAM_MAX_CHARS] );
+ }
+
// Add default
$default = isset( $settings[ApiBase::PARAM_DFLT] )
? $settings[ApiBase::PARAM_DFLT]
if ( !empty( $settings[ApiBase::PARAM_RANGE_ENFORCE] ) ) {
$item['enforcerange'] = true;
}
+ if ( isset( $settings[self::PARAM_MAX_BYTES] ) ) {
+ $item['maxbytes'] = $settings[self::PARAM_MAX_BYTES];
+ }
+ if ( isset( $settings[self::PARAM_MAX_CHARS] ) ) {
+ $item['maxchars'] = $settings[self::PARAM_MAX_CHARS];
+ }
if ( !empty( $settings[ApiBase::PARAM_DEPRECATED_VALUES] ) ) {
$deprecatedValues = array_keys( $settings[ApiBase::PARAM_DEPRECATED_VALUES] );
if ( is_array( $item['type'] ) ) {
"api-help-param-direction": "In which direction to enumerate:\n;newer:List oldest first. Note: $1start has to be before $1end.\n;older:List newest first (default). Note: $1start has to be later than $1end.",
"api-help-param-continue": "When more results are available, use this to continue.",
"api-help-param-no-description": "<span class=\"apihelp-empty\">(no description)</span>",
+ "api-help-param-maxbytes": "Cannot be longer than $1 {{PLURAL:$1|byte|bytes}}.",
+ "api-help-param-maxchars": "Cannot be longer than $1 {{PLURAL:$1|character|characters}}.",
"api-help-examples": "{{PLURAL:$1|Example|Examples}}:",
"api-help-permissions": "{{PLURAL:$1|Permission|Permissions}}:",
"api-help-permissions-granted-to": "{{PLURAL:$1|Granted to}}: $2",
"apierror-invalidurlparam": "Invalid value for <var>$1urlparam</var> (<kbd>$2=$3</kbd>).",
"apierror-invaliduser": "Invalid username \"$1\".",
"apierror-invaliduserid": "User ID <var>$1</var> is not valid.",
+ "apierror-maxbytes": "Parameter <var>$1</var> cannot be longer than $2 {{PLURAL:$2|byte|bytes}}",
+ "apierror-maxchars": "Parameter <var>$1</var> cannot be longer than $2 {{PLURAL:$2|character|characters}}",
"apierror-maxlag-generic": "Waiting for a database server: $1 {{PLURAL:$1|second|seconds}} lagged.",
"apierror-maxlag": "Waiting for $2: $1 {{PLURAL:$1|second|seconds}} lagged.",
"apierror-mimesearchdisabled": "MIME search is disabled in Miser Mode.",
"api-help-param-direction": "{{doc-apihelp-param|description=any standard \"dir\" parameter|noseealso=1}}",
"api-help-param-continue": "{{doc-apihelp-param|description=any standard \"continue\" parameter, or other parameter with the same semantics|noseealso=1}}",
"api-help-param-no-description": "Displayed on API parameters that lack any description",
+ "api-help-param-maxbytes": "Used to display the maximum allowed length of a parameter, in bytes.",
+ "api-help-param-maxchars": "Used to display the maximum allowed length of a parameter, in characters.",
"api-help-examples": "Label for the API help examples section\n\nParameters:\n* $1 - Number of examples to be displayed\n{{Identical|Example}}",
"api-help-permissions": "Label for the \"permissions\" section in the main module's help output.\n\nParameters:\n* $1 - Number of permissions displayed\n{{Identical|Permission}}",
"api-help-permissions-granted-to": "Used to introduce the list of groups each permission is assigned to.\n\nParameters:\n* $1 - Number of groups\n* $2 - List of group names, comma-separated",
"apierror-invalidurlparam": "{{doc-apierror}}\n\nParameters:\n* $1 - Module parameter prefix, e.g. \"bl\".\n* $2 - Key\n* $3 - Value.",
"apierror-invaliduser": "{{doc-apierror}}\n\nParameters:\n* $1 - User name that is invalid.",
"apierror-invaliduserid": "{{doc-apierror}}",
+ "apierror-maxbytes": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - Maximum allowed bytes.",
+ "apierror-maxchars": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - Maximum allowed characters.",
"apierror-maxlag-generic": "{{doc-apierror}}\n\nParameters:\n* $1 - Database is lag in seconds.",
"apierror-maxlag": "{{doc-apierror}}\n\nParameters:\n* $1 - Database lag in seconds.\n* $2 - Database server that is lagged.",
"apierror-mimesearchdisabled": "{{doc-apierror}}",
use MediaWiki\MediaWikiServices;
use MediaWiki\Shell\Shell;
-/** @deprecated use class constant instead */
-define( 'MW_DIFF_VERSION', '1.11a' );
-
/**
* @todo document
* @ingroup DifferenceEngine
* fixes important bugs or such to force cached diff views to
* clear.
*/
- const DIFF_VERSION = MW_DIFF_VERSION;
+ const DIFF_VERSION = '1.12';
/** @var int */
public $mOldid;
$key = false;
$cache = ObjectCache::getMainWANInstance();
if ( $this->mOldid && $this->mNewid ) {
+ // Check if subclass is still using the old way
+ // for backwards-compatibility
$key = $this->getDiffBodyCacheKey();
+ if ( $key === null ) {
+ $key = call_user_func_array(
+ [ $cache, 'makeKey' ],
+ $this->getDiffBodyCacheKeyParams()
+ );
+ }
// Try cache
if ( !$this->mRefreshCache ) {
$difftext = $cache->get( $key );
if ( $difftext ) {
wfIncrStats( 'diff_cache.hit' );
- $difftext = $this->localiseLineNumbers( $difftext );
+ $difftext = $this->localiseDiff( $difftext );
$difftext .= "\n<!-- diff cache key $key -->\n";
return $difftext;
} else {
wfIncrStats( 'diff_cache.uncacheable' );
}
- // Replace line numbers with the text in the user's language
+ // localise line numbers and title attribute text
if ( $difftext !== false ) {
- $difftext = $this->localiseLineNumbers( $difftext );
+ $difftext = $this->localiseDiff( $difftext );
}
return $difftext;
/**
* Returns the cache key for diff body text or content.
*
+ * @deprecated since 1.31, use getDiffBodyCacheKeyParams() instead
* @since 1.23
*
* @throws MWException
- * @return string
+ * @return string|null
*/
protected function getDiffBodyCacheKey() {
+ return null;
+ }
+
+ /**
+ * Get the cache key parameters
+ *
+ * Subclasses can replace the first element in the array to something
+ * more specific to the type of diff (e.g. "inline-diff"), or append
+ * if the cache should vary on more things. Overriding entirely should
+ * be avoided.
+ *
+ * @since 1.31
+ *
+ * @return array
+ * @throws MWException
+ */
+ protected function getDiffBodyCacheKeyParams() {
if ( !$this->mOldid || !$this->mNewid ) {
throw new MWException( 'mOldid and mNewid must be set to get diff cache key.' );
}
- return wfMemcKey( 'diff', 'version', self::DIFF_VERSION,
- 'oldid', $this->mOldid, 'newid', $this->mNewid );
+ $engine = $this->getEngine();
+ $params = [
+ 'diff',
+ $engine,
+ self::DIFF_VERSION,
+ "old-{$this->mOldid}",
+ "rev-{$this->mNewid}"
+ ];
+
+ if ( $engine === 'wikidiff2' ) {
+ $params[] = phpversion( 'wikidiff2' );
+ $params[] = $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' );
+ }
+
+ return $params;
}
/**
}
/**
- * Generates diff, to be wrapped internally in a logging/instrumentation
+ * Process $wgExternalDiffEngine and get a sane, usable engine
*
- * @param string $otext Old text, must be already segmented
- * @param string $ntext New text, must be already segmented
- * @return bool|string
- * @throws Exception
+ * @return bool|string 'wikidiff2', path to an executable, or false
*/
- protected function textDiff( $otext, $ntext ) {
- global $wgExternalDiffEngine, $wgContLang;
-
- $otext = str_replace( "\r\n", "\n", $otext );
- $ntext = str_replace( "\r\n", "\n", $ntext );
-
+ private function getEngine() {
+ global $wgExternalDiffEngine;
+ // We use the global here instead of Config because we write to the value,
+ // and Config is not mutable.
if ( $wgExternalDiffEngine == 'wikidiff' || $wgExternalDiffEngine == 'wikidiff3' ) {
wfDeprecated( "\$wgExternalDiffEngine = '{$wgExternalDiffEngine}'", '1.27' );
$wgExternalDiffEngine = false;
$wgExternalDiffEngine = false;
}
+ if ( is_string( $wgExternalDiffEngine ) && is_executable( $wgExternalDiffEngine ) ) {
+ return $wgExternalDiffEngine;
+ } elseif ( $wgExternalDiffEngine === false && function_exists( 'wikidiff2_do_diff' ) ) {
+ return 'wikidiff2';
+ } else {
+ // Native PHP
+ return false;
+ }
+ }
+
+ /**
+ * Generates diff, to be wrapped internally in a logging/instrumentation
+ *
+ * @param string $otext Old text, must be already segmented
+ * @param string $ntext New text, must be already segmented
+ * @return bool|string
+ */
+ protected function textDiff( $otext, $ntext ) {
+ global $wgContLang;
+
+ $otext = str_replace( "\r\n", "\n", $otext );
+ $ntext = str_replace( "\r\n", "\n", $ntext );
+
+ $engine = $this->getEngine();
+
// Better external diff engine, the 2 may some day be dropped
// This one does the escaping and segmenting itself
- if ( function_exists( 'wikidiff2_do_diff' ) && $wgExternalDiffEngine === false ) {
+ if ( $engine === 'wikidiff2' ) {
$wikidiff2Version = phpversion( 'wikidiff2' );
if (
$wikidiff2Version !== false &&
$text .= $this->debug( 'wikidiff2' );
return $text;
- } elseif ( $wgExternalDiffEngine !== false && is_executable( $wgExternalDiffEngine ) ) {
+ } elseif ( $engine !== false ) {
# Diff via the shell
$tmpDir = wfTempDir();
$tempName1 = tempnam( $tmpDir, 'diff_' );
fwrite( $tempFile2, $ntext );
fclose( $tempFile1 );
fclose( $tempFile2 );
- $cmd = [ $wgExternalDiffEngine, $tempName1, $tempName2 ];
+ $cmd = [ $engine, $tempName1, $tempName2 ];
$result = Shell::command( $cmd )
->execute();
$exitCode = $result->getExitCode();
);
}
$difftext = $result->getStdout();
- $difftext .= $this->debug( "external $wgExternalDiffEngine" );
+ $difftext .= $this->debug( "external $engine" );
unlink( $tempName1 );
unlink( $tempName2 );
" -->\n";
}
+ /**
+ * Localise diff output
+ *
+ * @param string $text
+ * @return string
+ */
+ private function localiseDiff( $text ) {
+ $text = $this->localiseLineNumbers( $text );
+ if ( $this->getEngine() === 'wikidiff2' &&
+ version_compare( phpversion( 'wikidiff2' ), '1.5.1', '>=' )
+ ) {
+ $text = $this->addLocalisedTitleTooltips( $text );
+ }
+ return $text;
+ }
+
/**
* Replace line numbers with the text in the user's language
*
return $this->msg( 'lineno' )->numParams( $matches[1] )->escaped();
}
+ /**
+ * Add title attributes for tooltips on moved paragraph indicators
+ *
+ * @param string $text
+ * @return string
+ */
+ private function addLocalisedTitleTooltips( $text ) {
+ return preg_replace_callback(
+ '/class="mw-diff-movedpara-(left|right)"/',
+ [ $this, 'addLocalisedTitleTooltipsCb' ],
+ $text
+ );
+ }
+
+ /**
+ * @param array $matches
+ * @return string
+ */
+ private function addLocalisedTitleTooltipsCb( array $matches ) {
+ $key = $matches[1] === 'right' ?
+ 'diff-paragraph-moved-toold' :
+ 'diff-paragraph-moved-tonew';
+ return $matches[0] . ' title="' . $this->msg( $key )->escaped() . '"';
+ }
+
/**
* If there are revisions between the ones being compared, return a note saying so.
*
/**
* Job to add recent change entries mentioning category membership changes
*
+ * This allows users to easily scan categories for recent page membership changes
+ *
* Parameters include:
* - pageId : page ID
* - revTimestamp : timestamp of the triggering revision
return false; // deleted?
}
+ // Cut down on the time spent in safeWaitForMasterPos() in the critical section
+ $dbr = $lb->getConnection( DB_REPLICA, [ 'recentchanges' ] );
+ if ( !$lb->safeWaitForMasterPos( $dbr ) ) {
+ $this->setLastError( "Timed out while pre-waiting for replica DB to catch up" );
+ return false;
+ }
+
// Use a named lock so that jobs for this page see each others' changes
$lockKey = "CategoryMembershipUpdates:{$page->getId()}";
$scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 3 );
return false;
}
- $dbr = $lb->getConnection( DB_REPLICA, [ 'recentchanges' ] );
- // Wait till the replica DB is caught up so that jobs for this page see each others' changes
+ // Wait till replica DB is caught up so that jobs for this page see each others' changes
if ( !$lb->safeWaitForMasterPos( $dbr ) ) {
$this->setLastError( "Timed out while waiting for replica DB to catch up" );
return false;
// between COMMIT and actual enqueueing of the CategoryMembershipChangeJob job.
$cutoffUnix -= self::ENQUEUE_FUDGE_SEC;
- // Get the newest revision that has a SRC_CATEGORIZE row...
+ // Get the newest page revision that has a SRC_CATEGORIZE row.
+ // Assume that category changes before it were already handled.
$row = $dbr->selectRow(
- [ 'revision', 'recentchanges' ],
+ 'revision',
[ 'rev_timestamp', 'rev_id' ],
[
'rev_page' => $page->getId(),
- 'rev_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( $cutoffUnix ) )
- ],
- __METHOD__,
- [ 'ORDER BY' => 'rev_timestamp DESC, rev_id DESC' ],
- [
- 'recentchanges' => [
- 'INNER JOIN',
+ 'rev_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( $cutoffUnix ) ),
+ 'EXISTS (' . $dbr->selectSQLText(
+ 'recentchanges',
+ '1',
[
'rc_this_oldid = rev_id',
'rc_source' => RecentChange::SRC_CATEGORIZE,
// Allow rc_cur_id or rc_timestamp index usage
'rc_cur_id = rev_page',
- 'rc_timestamp >= rev_timestamp'
+ 'rc_timestamp = rev_timestamp'
]
- ]
- ]
+ ) . ')'
+ ],
+ __METHOD__,
+ [ 'ORDER BY' => 'rev_timestamp DESC, rev_id DESC' ]
);
// Only consider revisions newer than any such revision
if ( $row ) {
);
if ( $charsLeft < 0 ) {
- return $keyspace . ':##' . md5( implode( ':', $args ) );
+ return $keyspace . ':BagOStuff-long-key:##' . md5( implode( ':', $args ) );
}
return $keyspace . ':' . implode( ':', $args );
const TTL_LAGGED = 30;
/** Idiom for delete() for "no hold-off" */
const HOLDOFF_NONE = 0;
+ /** Idiom for set() for "do not augment the storage medium TTL" */
+ const STALE_TTL_NONE = 0;
+
/** Idiom for getWithSetCallback() for "no minimum required as-of timestamp" */
const MIN_TIMESTAMP_NONE = 0.0;
* they certainly should not see ones that ended up getting rolled back.
* Default: false
* - lockTSE : if excessive replication/snapshot lag is detected, then store the value
- * with this TTL and flag it as stale. This is only useful if the reads for
- * this key use getWithSetCallback() with "lockTSE" set.
+ * with this TTL and flag it as stale. This is only useful if the reads for this key
+ * use getWithSetCallback() with "lockTSE" set. Note that if "staleTTL" is set
+ * then it will still add on to this TTL in the excessive lag scenario.
* Default: WANObjectCache::TSE_NONE
* - staleTTL : Seconds to keep the key around if it is stale. The get()/getMulti()
* methods return such stale values with a $curTTL of 0, and getWithSetCallback()
* will call the regeneration callback in such cases, passing in the old value
* and its as-of time to the callback. This is useful if adaptiveTTL() is used
* on the old value's as-of time when it is verified as still being correct.
- * Default: 0.
+ * Default: WANObjectCache::STALE_TTL_NONE.
* @note Options added in 1.28: staleTTL
* @return bool Success
*/
final public function set( $key, $value, $ttl = 0, array $opts = [] ) {
$now = microtime( true );
$lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
+ $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : self::STALE_TTL_NONE;
$age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
$lag = isset( $opts['lag'] ) ? $opts['lag'] : 0;
- $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : 0;
// Do not cache potentially uncommitted data as it might get rolled back
if ( !empty( $opts['pending'] ) ) {
*/
public static function anchorencode( $parser, $text ) {
$text = $parser->killMarkers( $text );
- return (string)substr( $parser->guessSectionNameFromWikiText( $text ), 1 );
+ $section = (string)substr( $parser->guessSectionNameFromWikiText( $text ), 1 );
+ return Sanitizer::safeEncodeAttribute( $section );
}
public static function special( $parser, $text ) {
'{' => '{',
'}' => '}', // prevent unpaired language conversion syntax
'[' => '[',
+ ']' => ']',
"''" => '''',
'ISBN' => 'ISBN',
'RFC' => 'RFC',
$this->setHeaders();
$this->outputHeader();
- $out->addModuleStyles( 'mediawiki.special.userrights.styles' );
+ $out->addModuleStyles( 'mediawiki.special' );
$this->addHelpLink( 'Help:Assigning permissions' );
$this->switchForm();
"uploadstash-file-not-found-no-local-path": "Няма лякальнага шляху да маштабаванага элемэнту.",
"uploadstash-file-not-found-no-object": "Не атрымалася стварыць лякальны аб’ект файлу для мініятуры.",
"uploadstash-file-not-found-no-remote-thumb": "Памылка атрыманьня мініятуры: $1\nURL = $2",
+ "uploadstash-file-not-found-missing-content-type": "Адсутнічае загаловак тыпу зьместу.",
"invalid-chunk-offset": "Няслушнае зрушэньне фрагмэнту",
"img-auth-accessdenied": "Доступ забаронены",
"img-auth-nopathinfo": "Адсутнічае PATH_INFO.\nВаш сэрвэр не ўстаноўлены на пропуск гэтай інфармацыі.\nМагчма, ён працуе праз CGI і не падтрымлівае img_auth.\nГлядзіце https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
"minutes-abbrev": "$1дакъ.",
"hours-abbrev": "$1саат",
"bad_image_list": "Формат бойле олмалы:\n\nЭр сатыр * ишаретинен башламалы. Сатырнынъ биринджи багълантысы къошмагъа ясакълангъан файлгъа багъланмалы.\nШу сатырда илеридеки багълантылар истисна олурлар, яни шу саифелерде ишбу файл къулланмакъ мумкюн.",
+ "variantname-crh": "Lat./Кир.",
+ "variantname-crh-latn": "Latin",
+ "variantname-crh-cyrl": "Кирил",
"metadata": "Ресим деталлери",
"metadata-help": "Файлда (адетиндже ракъамлы камера ве сканерлернен къошулгъан) иляве малюматы бар. Эгер бу файл яратылгъандан сонъ денъиштирильсе эди, бельки де базы параметрлер эскирди.",
"metadata-expand": "Тафсилятны косьтер",
"logentry-move-move_redir-noredirect": "$1 адлы къулланыджы $3 саифесининъ адыны ёнетме узеринден янъы бир ёнетме къалдырмайып $4 деп {{GENDER:$2|денъиштирди}}",
"searchsuggest-search": "Къыдыр",
"searchsuggest-containing": "ичинде бу олгъан...",
- "variantname-crh": "Lat./Кир.",
- "variantname-crh-latn": "Latin",
- "variantname-crh-cyrl": "Кирил",
"pagelang-language": "Тиль"
}
"minutes-abbrev": "$1daq.",
"hours-abbrev": "$1saat",
"bad_image_list": "Format böyle olmalı:\n\nEr satır * işaretinen başlamalı. Satırnıñ birinci bağlantısı qoşmağa yasaqlanğan faylğa bağlanmalı.\nŞu satırda ilerideki bağlantılar istisna olur, yani şu saifelerde işbu fayl qullanmaq mümkün.",
+ "variantname-crh": "Lat./Кир.",
+ "variantname-crh-latn": "Latin",
+ "variantname-crh-cyrl": "Кирил",
"metadata": "Resim detalleri",
"metadata-help": "Faylda (adetince raqamlı kamera ve skanerlernen qoşulğan) ilâve malümatı bar. Eger bu fayl yaratılğandan soñ deñiştirilse edi, belki de bazı parametrler eskirdi.",
"metadata-expand": "Tafsilâtnı köster",
"logentry-move-move_redir-noredirect": "$1 adlı qullanıcı $3 saifesiniñ adını yönetme üzerinden yañı bir yönetme qaldırmayıp $4 dep {{GENDER:$2|deñiştirdi}}",
"searchsuggest-search": "Qıdır",
"searchsuggest-containing": "içinde bu olğan...",
- "variantname-crh": "Lat./Кир.",
- "variantname-crh-latn": "Latin",
- "variantname-crh-cyrl": "Кирил",
"pagelang-language": "Til"
}
"diff-multi-sameuser": "({{PLURAL:$1|Eine dazwischenliegende Version desselben Benutzers wird|$1 dazwischenliegende Versionen desselben Benutzers werden}} nicht angezeigt)",
"diff-multi-otherusers": "({{PLURAL:$1|Eine dazwischenliegende Version|$1 dazwischenliegende Versionen}} von {{PLURAL:$2|einem anderen Benutzer|$2 Benutzern}} {{PLURAL:$1|wird|werden}} nicht angezeigt)",
"diff-multi-manyusers": "({{PLURAL:$1|$1 dazwischenliegende Versionen}} von mehr als {{PLURAL:$2|$2 Benutzern}}, die nicht angezeigt werden)",
+ "diff-paragraph-moved-tonew": "Der Absatz wurde verschoben. Klicken, um zur neuen Stelle zu springen.",
+ "diff-paragraph-moved-toold": "Der Absatz wurde verschoben. Klicken, um zur alten Stelle zu springen.",
"difference-missing-revision": "{{PLURAL:$2|Eine Version|$2 Versionen}} dieser Unterschiedsanzeige ($1) {{PLURAL:$2|wurde|wurden}} nicht gefunden.\n\nDieser Fehler wird normalerweise von einem veralteten Link zur Versionsgeschichte einer Seite verursacht, die zwischenzeitlich gelöscht wurde.\nEinzelheiten sind im [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} Lösch-Logbuch] vorhanden.",
"searchresults": "Suchergebnisse",
"searchresults-title": "Suchergebnisse für „$1“",
"diff-multi-sameuser": "({{PLURAL:$1|Μία ενδιάμεση αναθεώρηση|$1 ενδιάμεσες αναθεωρήσεις}} από τον ίδιο χρήστη δεν εμφανίζεται)",
"diff-multi-otherusers": "({{PLURAL:$1|Μία ενδιάμεση έκδοση|$1 ενδιάμεσες εκδόσεις}} από {{PLURAL:$2|ένα χρήστη|$2 χρήστες}} δεν εμφανίζ{{PLURAL:$1|εται|ονται}})",
"diff-multi-manyusers": "({{PLURAL:$1|Μία ενδιάμεση αναθεώρηση|$1 ενδιάμεσες αναθεωρήσεις}} από περισσότερο από $2 {{PLURAL:$2|χρήστη|χρήστες}} δεν εμφανίζ{{PLURAL:$1|εται|ονται}})",
+ "diff-paragraph-moved-tonew": "Η παράγραφος αφαιρέθηκε. Κάντε κλικ για να πάτε σε νέα τοποθεσία.",
+ "diff-paragraph-moved-toold": "Η παράγραφος αφαιρέθηκε. Πατήστε στο κουμπί για να πάτε σε προηγούμενη τοποθεσία.",
"difference-missing-revision": "{{PLURAL:$2|Μία αναθεώρηση|$2 αναθεωρήσεις}} αυτής της διαφοράς ($1) δεν {{PLURAL:$2|μπόρεσε να βρεθεί|μπόρεσαν να βρεθούν}}.\n\nΑυτό συνήθως προκαλείται από παλιό σύνδεσμο διαφοράς προς σελίδα που έχει διαγραφεί.\nΛεπτομέρειες θα βρείτε στο [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} ημερολόγιο καταγραφής διαγραφών].",
"searchresults": "Αποτελέσματα αναζήτησης",
"searchresults-title": "Αποτελέσματα αναζήτησης για \"$1\"",
"diff-multi-sameuser": "({{PLURAL:$1|One intermediate revision|$1 intermediate revisions}} by the same user not shown)",
"diff-multi-otherusers": "({{PLURAL:$1|One intermediate revision|$1 intermediate revisions}} by {{PLURAL:$2|one other user|$2 users}} not shown)",
"diff-multi-manyusers": "({{PLURAL:$1|One intermediate revision|$1 intermediate revisions}} by more than $2 {{PLURAL:$2|user|users}} not shown)",
+ "diff-paragraph-moved-tonew": "Paragraph was moved. Click to jump to new location.",
+ "diff-paragraph-moved-toold": "Paragraph was moved. Click to jump to old location.",
"difference-missing-revision": "{{PLURAL:$2|One revision|$2 revisions}} of this difference ($1) {{PLURAL:$2|was|were}} not found.\n\nThis is usually caused by following an outdated diff link to a page that has been deleted.\nDetails can be found in the [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log].",
"search-summary": "",
"searchresults": "Search results",
"diff-multi-sameuser": "({{PLURAL:$1|גרסת ביניים אחת|$1 גרסאות ביניים}} של אותו משתמש {{PLURAL:$1|אינה מוצגת|אינן מוצגות}})",
"diff-multi-otherusers": "({{PLURAL:$1|גרסת ביניים אחת|$1 גרסאות ביניים}} של {{PLURAL:$2|משתמש אחר אחד|$2 משתמשים}} {{PLURAL:$1|אינה מוצגת|אינן מוצגות}})",
"diff-multi-manyusers": "({{PLURAL:$1|גרסת ביניים אחת|$1 גרסאות ביניים}} של יותר {{PLURAL:$2|ממשתמש אחד|מ־$2 משתמשים}} {{PLURAL:$1|אינה מוצגת|אינן מוצגות}})",
+ "diff-paragraph-moved-tonew": "הפיסקה הועברה. ניתן ללחוץ כאן כדי לעבור למיקומה החדש.",
+ "diff-paragraph-moved-toold": "הפיסקה הועברה. ניתן ללחוץ כאן כדי לעבור למיקומה הישן.",
"difference-missing-revision": "{{PLURAL:$2|גרסה אחת|$2 גרסאות}} מתוך הגרסאות שביקשת להשוות ($1) {{PLURAL:$2|לא נמצאה|לא נמצאו}}.\n\nזה נגרם בדרך־כלל עקב לחיצה על קישור ישן להבדלים בין גרסאות של דף שנמחק.\nאפשר למצוא פרטים ב[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} יומן המחיקות].",
"searchresults": "תוצאות החיפוש",
"searchresults-title": "תוצאות החיפוש \"$1\"",
"nosuchusershort": "「$1」という名前の利用者は存在しません。\n綴りを確認してください。",
"nouserspecified": "利用者名を指定してください。",
"login-userblocked": "この利用者はブロックされています。ログインは拒否されます。",
- "wrongpassword": "パスワードが間違っています。 \nもう一度やり直してください。",
+ "wrongpassword": "利用者名またはパスワードが間違っています。 \nもう一度やり直してください。",
"wrongpasswordempty": "パスワードを空欄にはできません。\nもう一度やり直してください。",
"passwordtooshort": "パスワードは {{PLURAL:$1|$1 文字}}以上にしてください。",
"passwordtoolong": "パスワードは {{PLURAL:$1|$1 文字}}以下にしてください。",
"nosuchusershort": "De Benotzernumm \"$1\" gëtt et net.\nKuckt w.e.g. op d'Schreifweis richteg ass.",
"nouserspecified": "Gitt w.e.g. e Benotzernumm un.",
"login-userblocked": "Dëse Benotzer ass gespaart. Aloggen ass net erlaabt.",
- "wrongpassword": "Dir hutt e falscht (oder kee) Passwuert aginn. Probéiert w.e.g. nach eng Kéier.",
+ "wrongpassword": "De Benotzernumm oder d'Passwuert si falsch.\nProbéiert w.e.g. nach eng Kéier.",
"wrongpasswordempty": "D'Passwuert dat Dir aginn hutt war eidel.\nProbéiert w.e.g. nach eng Kéier.",
"passwordtooshort": "Passwierder musse mindestens {{PLURAL:$1|1 Zeeche|$1 Zeeche}} laang sinn.",
"passwordtoolong": "Passwierder kënnen net méi laang wéi {{PLURAL:$1|1 Zeeche|$1 Zeeche}} sinn.",
"rcfilters-savedqueries-apply-and-setdefault-label": "Standardfilter uleeën",
"rcfilters-savedqueries-cancel-label": "Ofbriechen",
"rcfilters-savedqueries-add-new-title": "Aktuell Filter-Astellunge späicheren",
- "rcfilters-savedqueries-already-saved": "Dës Filtere si scho gespäichert",
+ "rcfilters-savedqueries-already-saved": "Dës Filtere si scho gespäichert. Ännert Är Astellunge fir en neie Gespäicherte Filter unzeleeën.",
"rcfilters-restore-default-filters": "Standardfiltere restauréieren",
"rcfilters-clear-all-filters": "All Filteren eidelmaachen",
"rcfilters-show-new-changes": "Rezentst Ännerunge weisen",
"rcfilters-filterlist-feedbacklink": "Sot eis wat Dir vun dësen (neien) Filterméiglechkeeten haalt",
"rcfilters-highlightbutton-title": "Resultater ervirhiewen",
"rcfilters-highlightmenu-title": "Eng Faarf eraussichen",
+ "rcfilters-highlightmenu-help": "Sicht eng Faarf eraus fir dës Eegenschaft ervirzehiewen.",
"rcfilters-filterlist-noresults": "Keng Filtere fonnt",
"rcfilters-noresults-conflict": "Näischt fonnt well d'Sichcritère sech widderspriechen",
"rcfilters-filter-editsbyself-label": "Ännerunge vun Iech",
"filehist-comment": "Komentārs",
"imagelinks": "Faila lietojums",
"linkstoimage": "{{PLURAL:$1|Šajās $1 lapās ir saites|Šajā lapā ir saite|Šajās $1 lapās ir saites}} uz šo failu:",
+ "linkstoimage-more": "Uz šo failu ir saites vairāk nekā $1 {{PLURAL:$1|lapās|lapā|lapās}}.\nŠajā sarakstā ir tikai {{PLURAL:$1|pirmās $1 saistītās lapas|pirmā $1 saistītā lapa|pirmās $1 saistītās lapas}} uz šo failu.\nPieejams arī [[Special:WhatLinksHere/$2|pilns saraksts]].",
"nolinkstoimage": "Nevienā lapā nav norāžu uz šo attēlu.",
"morelinkstoimage": "Skatīt [[Special:WhatLinksHere/$1|vairāk saites]] uz šo failu.",
"linkstoimage-redirect": "$1 (faila pāradresācija) $2",
"nosuchusershort": "Não existe nenhum utilizador com o nome \"$1\".\nVerifique o nome que introduziu.",
"nouserspecified": "Tem de especificar um nome de utilizador.",
"login-userblocked": "Este utilizador está bloqueado. Não é permitido o acesso.",
- "wrongpassword": "A palavra-passe que introduziu é inválida. Tente novamente, por favor.",
+ "wrongpassword": "O nome de utilizador ou a palavra-passe inseridos estão incorretos.\nTente novamente, por favor.",
"wrongpasswordempty": "A palavra-passe não foi introduzida. \nIntroduza-a, por favor.",
"passwordtooshort": "A palavra-passe deve ter no mínimo $1 {{PLURAL:$1|carácter|caracteres}}.",
"passwordtoolong": "A palavra-passe não pode exceder $1 {{PLURAL:$1|carácter|caracteres}}.",
"diff-multi-sameuser": "(Há {{PLURAL:$1|uma edição intermédia do mesmo utilizador que não está a ser apresentada|$1 edições intermédias do mesmo utilizador que não estão a ser apresentadas}})",
"diff-multi-otherusers": "(Há {{PLURAL:$1|uma revisão intermédia|$1 revisões intermédias}} de {{PLURAL:$2|outro utilizador|$2 utilizadores}} que não {{PLURAL:$1|está a ser apresentada|estão a ser apresentadas}})",
"diff-multi-manyusers": "({{PLURAL:$1|Uma edição intermédia|$1 edições intermédias}} de mais de {{PLURAL:$2|um utilizador|$2 utilizadores}} não {{PLURAL:$1|apresentada|apresentadas}})",
+ "diff-paragraph-moved-tonew": "O parágrafo foi movido. Clique para saltar para a nova posição.",
+ "diff-paragraph-moved-toold": "O parágrafo foi movido. Clique para saltar para a posição anterior.",
"difference-missing-revision": "{{PLURAL:$2|Uma revisão|$2 revisões}} desta diferença ($1) não {{PLURAL:$2|foi encontrada|foram encontradas}}.\n\nIsto é geralmente causado por seguir uma ligação de histórico desatualizada para uma página que foi eliminada.\nOs detalhes podem ser encontrados no [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registo de eliminação].",
"searchresults": "Resultados da pesquisa",
"searchresults-title": "Resultados da pesquisa de \"$1\"",
"rcfilters-savedqueries-apply-and-setdefault-label": "Criar filtro padrão",
"rcfilters-savedqueries-cancel-label": "Cancelar",
"rcfilters-savedqueries-add-new-title": "Gravar as configurações de filtros atuais",
- "rcfilters-savedqueries-already-saved": "Estes filtros já foram gravados",
+ "rcfilters-savedqueries-already-saved": "Estes filtros já foram gravados. Altera as suas configurações para criar um novo filtro gravado.",
"rcfilters-restore-default-filters": "Restaurar os filtros padrão",
"rcfilters-clear-all-filters": "Limpar todos os filtros",
"rcfilters-show-new-changes": "Mostrar as mudanças mais recentes",
"default-skin-not-found-no-skins": "O tema padrão da sua wiki definido em <code dir=\"ltr\">$wgDefaultSkin</code>, <code>$1</code>, não está disponível.\n\nNão tem nenhum tema instalado.\n\n; Se acabou de instalar ou atualizar o MediaWiki:\n: Provavelmente instalou-o a partir do git, ou diretamente do código fonte usando outro método. O comportamento é o esperado. O MediaWiki 1.24 e versões mais recentes não incluem qualquer tema no repositório principal. Tente instalar temas a partir do [https://www.mediawiki.org/wiki/Category:All_skins diretório de temas da mediawiki.org], assim:\n:* Descarregue o [https://www.mediawiki.org/wiki/Download tarball de instalação], que contém vários temas e extensões. Pode copiar o diretório <code>skins/</code> nele incluído.\n:* Descarregue tarballs de temas individuais, da [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Use o Git para descarregar temas].\n: Se é programador(a) do MediaWiki, isto não deverá interferir com o seu repositório git. Consulte [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual: Configuração de Temas] para saber como ativar temas e escolher o tema padrão.",
"default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (ativado)",
"default-skin-not-found-row-disabled": "* <code>$1</code> / $2 (<strong>desativado</strong>)",
- "mediastatistics": "Estatísticas multimédia",
+ "mediastatistics": "Estatísticas de multimédia",
"mediastatistics-summary": "Estatísticas sobre os tipos de ficheiro carregados. Inclui apenas a versão mais recente de cada ficheiro. Versões antigas ou eliminadas não estão incluídas.",
"mediastatistics-nbytes": "{{PLURAL:$1|$1 byte|$1 bytes}} ($2; $3%)",
"mediastatistics-bytespertype": "Tamanho total dos ficheiros desta secção: {{PLURAL:$1|$1 byte|$1 bytes}} ($2; $3%).",
"diff-multi-sameuser": "This message appears in the revision history of a page when comparing two versions which aren't consecutive, and the intermediate revisions were all created by the same user as the new revision.\n\nParameters:\n* $1 - the number of revisions\n{{Related|Diff-multi}}",
"diff-multi-otherusers": "This message appears in the revision history of a page when comparing two versions which aren't consecutive, and at least one of the intermediate revisions was created by a user other than the user who created the new revision.\n\nParameters:\n* $1 - the number of revisions\n* $2 - the number of distinct other users who made those revisions\n{{Related|Diff-multi}}",
"diff-multi-manyusers": "This message appears in the revision history of a page when comparing two versions which aren't consecutive, and the intermediate revisions have been edited by more than 100 users.\n\nParameters:\n* $1 - the number of revisions, will always be 101 or more\n* $2 - the number of users that were found, which was limited at 100\n{{Related|Diff-multi}}",
+ "diff-paragraph-moved-tonew": "Explaining title tag for the indicating symbols when a paragraph was moved hinting to the new location in the Diff view.",
+ "diff-paragraph-moved-toold": "Explaining title tag for the indicating symbols when a paragraph was moved hinting to the old location in the Diff view.",
"difference-missing-revision": "Text displayed when the requested revision does not exist using a diff link.\n\nExample: [{{canonicalurl:Project:News|diff=426850&oldid=99999999}} Diff with invalid revision#]\n\nParameters:\n* $1 - the list of missing revisions IDs\n* $2 - the number of items in $1 (one or two)",
"search-summary": "{{doc-specialpagesummary|search}}",
"searchresults": "This is the title of the page that contains the results of a search.\n\n{{Identical|Search results}}",
"variantname-kk-latn": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
"variantname-kk-arab": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
"variantname-kk": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
- "variantname-crh-cyrl": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
- "variantname-crh-latn": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
- "variantname-crh": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
"variantname-ku-arab": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
"variantname-ku-latn": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
"variantname-ku": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
"variantname-uz": "{{optional}}",
"variantname-uz-latn": "{{optional}}",
"variantname-uz-cyrl": "{{optional}}",
+ "variantname-crh": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
+ "variantname-crh-latn": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
+ "variantname-crh-cyrl": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
"metadata": "The title of a section on an image description page, with information and data about the image. For example of message in use see [[commons:File:Titan-crystal_bar.JPG|Commons]].\n{{Identical|Metadata}}",
"metadata-help": "This message is followed by a table with metadata.",
"metadata-expand": "On an image description page, there is mostly a table containing data (metadata) about the image. The most important data are shown, but if you click on this link, you can see more data and information. For the link to hide back the less important data, see {{msg-mw|Metadata-collapse}}.",
"diff-multi-sameuser": "({{PLURAL:$1|1=Vmesna redakcija|$1 vmesna redakcija|$1 vmesni redakciji|$1 vmesne redakcije|$1 vmesnih redakcij}} istega uporabnika {{PLURAL:$1|ni prikazana|nista prikazani|niso prikazane|ni prikazanih}})",
"diff-multi-otherusers": "({{PLURAL:$1|1=Vmesna redakcija|$1 vmesna redakcija|$1 vmesni redakciji|$1 vmesne redakcije|$1 vmesnih redakcij}} {{PLURAL:$2|1=drugega uporabnika|$2 uporabnikov}} {{PLURAL:$1|ni prikazana|nista prikazani|niso prikazane|ni prikazanih}})",
"diff-multi-manyusers": "({{PLURAL:$1|$1 vmesna redakcija|$1 vmesni redakciji|$1 vmesne redakcije|$1 vmesnih redakcij}} več kot $2 {{PLURAL:$2|uporabnika|uporabnikov}} {{PLURAL:$1|ni prikazana|nista prikazani|niso prikazane|ni prikazanih}})",
+ "diff-paragraph-moved-tonew": "Odstavek je premaknjen. Kliknite, da skočite na novo nahajališče.",
+ "diff-paragraph-moved-toold": "Odstavek je bil premaknjen. Kliknite, da skočite na staro nahajališče.",
"difference-missing-revision": "{{PLURAL:$2|Ene redakcije|$2 redakcij}} razlike ($1) {{PLURAL:$2|nisem}} našel.\n\nPo navadi se to zgodi, ko sledite zastareli povezavi na razliko redakcij strani, ki jo je nekdo izbrisal.\nPodrobnosti lahko najdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} dnevniku brisanja].",
"searchresults": "Izid iskanja",
"searchresults-title": "Zadetki za povpraševanje »$1«",
"apisandbox": "API 沙盒",
"apisandbox-jsonly": "需要JavaScript以使用API沙盒。",
"apisandbox-api-disabled": "API在该网站停用。",
- "apisandbox-intro": "使用这个页面来试验<strong>MediaWiki Web 服务应用程序接口(API)</strong>。\n欲知API使用详情,请参阅[[mw:API:Main page|API文档]]。\n例如:[https://www.mediawiki.org/wiki/API#A_simple_example 取得某个主页的内容],然后选择一个操作来看更多范例。\n\n请注意,虽然这是一个沙盒,但是您在这个页面上的改动可能会修改维基。",
+ "apisandbox-intro": "使用这个页面来试验<strong>MediaWiki Web 服务应用程序接口(API)</strong>。欲知API使用详情,请参阅[[mw:API:Main page|API文档]]。例如:[https://www.mediawiki.org/wiki/API#A_simple_example 取得某个主页的内容],然后选择一个操作来看更多范例。\n\n请注意,虽然这是一个沙盒,但是您在这个页面上的改动可能会修改维基。",
"apisandbox-fullscreen": "展开面板",
"apisandbox-fullscreen-tooltip": "展开沙盒面板以填充浏览器窗口。",
"apisandbox-unfullscreen": "显示页面",
"articleexists": "该名称的页面已存在,或者您使用的名称无效。请另选一名。",
"cantmove-titleprotected": "您无法将页面移动到该位置,因为新标题已被保护以防止创建。",
"movetalk": "移动关联的讨论页",
- "move-subpages": "移动子页面(上至$1页)",
+ "move-subpages": "移动子页面(最多$1页)",
"move-talk-subpages": "如果可能,移动子对话页面(上至$1页)",
"movepage-page-exists": "页面$1已存在,无法自动覆盖。",
"movepage-page-moved": "页面$1已经移动到$2。",
"variantname-gan-hans": "赣语(简体)",
"variantname-gan-hant": "赣语(繁体)",
"variantname-kk-cyrl": "kk-cyrl",
+ "variantname-crh-latn": "克里米亚鞑靼文(拉丁)",
+ "variantname-crh-cyrl": "克里米亚鞑靼文(西里尔)",
"metadata": "元数据",
"metadata-help": "此文件中包含有额外的信息。这些信息可能是由数码相机或扫描仪在创建或数字化过程中所添加的。如果文件自初始状态已受到修改,一些详细说明可能无法反映修改后的文件。",
"metadata-expand": "显示详细资料",
"rcfilters-filtergroup-authorship": "貢獻的作者",
"rcfilters-filter-editsbyself-label": "您的編輯",
"rcfilters-filter-editsbyself-description": "您的貢獻",
- "rcfilters-filter-editsbyother-label": "其他人的更改",
+ "rcfilters-filter-editsbyother-label": "其他人的變更",
"rcfilters-filter-editsbyother-description": "除了您以外的所有更改",
"rcfilters-filtergroup-userExpLevel": "使用者註冊及經驗",
"rcfilters-filter-user-experience-level-registered-label": "已註冊",
* Throw an error to the user. Doesn't respect --quiet, so don't use
* this for non-error output
* @param string $err The error to display
- * @param int $die If > 0, go ahead and die out using this int as the code
+ * @param int $die Deprecated since 1.31, use Maintenance::fatalError() instead
*/
protected function error( $err, $die = 0 ) {
+ if ( intval( $die ) !== 0 ) {
+ wfDeprecated( __METHOD__ . '( $err, $die )', '1.31' );
+ $this->fatalError( $err, intval( $die ) );
+ }
$this->outputChanneled( false );
if ( PHP_SAPI == 'cli' ) {
fwrite( STDERR, $err . "\n" );
} else {
print $err;
}
- $die = intval( $die );
- if ( $die > 0 ) {
- die( $die );
- }
+ }
+
+ /**
+ * Output a message and terminate the current script.
+ *
+ * @param string $msg Error message
+ * @param int $exitCode PHP exit status. Should be in range 1-254.
+ * @since 1.31
+ */
+ protected function fatalError( $msg, $exitCode = 1 ) {
+ $this->error( $msg );
+ exit( $exitCode );
}
private $atLineStart = true;
$joined = implode( ', ', $missing );
$msg = "The following extensions are required to be installed "
. "for this script to run: $joined. Please enable them and then try again.";
- $this->error( $msg, 1 );
+ $this->fatalError( $msg );
}
}
# Abort if called from a web server
if ( isset( $_SERVER ) && isset( $_SERVER['REQUEST_METHOD'] ) ) {
- $this->error( 'This script must be run from the command line', true );
+ $this->fatalError( 'This script must be run from the command line' );
}
if ( $IP === null ) {
- $this->error( "\$IP not set, aborting!\n" .
- '(Did you forget to call parent::__construct() in your maintenance script?)', 1 );
+ $this->fatalError( "\$IP not set, aborting!\n" .
+ '(Did you forget to call parent::__construct() in your maintenance script?)' );
}
# Make sure we can handle script parameters
if ( !defined( 'HPHP_VERSION' ) && !ini_get( 'register_argc_argv' ) ) {
- $this->error( 'Cannot get command line arguments, register_argc_argv is set to false', true );
+ $this->fatalError( 'Cannot get command line arguments, register_argc_argv is set to false' );
}
// Send PHP warnings and errors to stderr instead of stdout.
}
if ( !is_readable( $settingsFile ) ) {
- $this->error( "A copy of your installation's LocalSettings.php\n" .
+ $this->fatalError( "A copy of your installation's LocalSettings.php\n" .
"must exist and be readable in the source directory.\n" .
- "Use --conf to specify it.", true );
+ "Use --conf to specify it." );
}
$wgCommandLineMode = true;
fwrite( $this->stderr, $string . "\n" );
}
}
-
- function fatalError( $msg ) {
- $this->error( "$msg\n", 1 );
- }
}
class ExportProgressFilter extends DumpFilter {
$content = file_get_contents( $this->getOption( 'file' ) );
MediaWiki\restoreWarnings();
if ( $content === false ) {
- $this->error( 'Unable to open input file', 1 );
+ $this->fatalError( 'Unable to open input file' );
}
$filename = basename( $this->getOption( 'file' ) );
$loops = $this->getOption( 'loops', 1 );
if ( $loops < 1 ) {
- $this->error( 'Invalid number of loops specified', true );
+ $this->fatalError( 'Invalid number of loops specified' );
}
$startUsage = getrusage();
$startTime = microtime( true );
public function execute() {
global $wgUseSquid, $wgSquidServers;
if ( !$wgUseSquid ) {
- $this->error( "Squid purge benchmark doesn't do much without squid support on.", true );
+ $this->fatalError( "Squid purge benchmark doesn't do much without squid support on." );
} else {
$this->output( "There are " . count( $wgSquidServers ) . " defined squid servers:\n" );
if ( $this->hasOption( 'count' ) ) {
public function execute() {
$html = file_get_contents( $this->getOption( 'file' ) );
if ( $html === false ) {
- $this->error( "Unable to open input file", 1 );
+ $this->fatalError( "Unable to open input file" );
}
if ( $this->hasOption( 'driver' ) || $this->hasOption( 'tidy-config' ) ) {
$config = json_decode( $this->getOption( 'tidy-config', '{}' ), true );
if ( !is_array( $config ) ) {
- $this->error( "Invalid JSON tidy config", 1 );
+ $this->fatalError( "Invalid JSON tidy config" );
}
$config += [ 'driver' => $this->getOption( 'driver', 'RemexHtml' ) ];
$driver = MWTidy::factory( $config );
} else {
$driver = MWTidy::singleton();
if ( !$driver ) {
- $this->error( "Tidy disabled or not installed", 1 );
+ $this->fatalError( "Tidy disabled or not installed" );
}
}
} elseif ( $this->hasOption( "userid" ) ) {
$user = User::newFromId( $this->getOption( 'userid' ) );
} else {
- $this->error( "A \"user\" or \"userid\" must be set to change the password for", true );
+ $this->fatalError( "A \"user\" or \"userid\" must be set to change the password for" );
}
if ( !$user || !$user->getId() ) {
- $this->error( "No such user: " . $this->getOption( 'user' ), true );
+ $this->fatalError( "No such user: " . $this->getOption( 'user' ) );
}
$password = $this->getOption( 'password' );
try {
$user->saveSettings();
$this->output( "Password set for " . $user->getName() . "\n" );
} catch ( PasswordError $pwe ) {
- $this->error( $pwe->getText(), true );
+ $this->fatalError( $pwe->getText() );
}
}
}
// Maybe they're using mediawiki/vendor?
$lockLocation = "$IP/vendor/composer.lock";
if ( !file_exists( $lockLocation ) ) {
- $this->error(
- 'Could not find composer.lock file. Have you run "composer install --no-dev"?',
- 1
+ $this->fatalError(
+ 'Could not find composer.lock file. Have you run "composer install --no-dev"?'
);
}
}
}
}
if ( $found ) {
- $this->error(
+ $this->fatalError(
'Error: your composer.lock file is not up to date. ' .
- 'Run "composer update --no-dev" to install newer dependencies',
- 1
+ 'Run "composer update --no-dev" to install newer dependencies'
);
} else {
// We couldn't find any out-of-date dependencies, so assume everything is ok!
$username = wfMessage( 'spambot_username' )->text();
$wgUser = User::newSystemUser( $username );
if ( !$wgUser ) {
- $this->error( "Invalid username specified in 'spambot_username' message: $username", true );
+ $this->fatalError( "Invalid username specified in 'spambot_username' message: $username" );
}
// Hack: Grant bot rights so we don't flood RecentChanges
$wgUser->addGroup( 'bot' );
$spec = $this->getArg();
$like = LinkFilter::makeLikeArray( $spec );
if ( !$like ) {
- $this->error( "Not a valid hostname specification: $spec", true );
+ $this->fatalError( "Not a valid hostname specification: $spec" );
}
if ( $this->hasOption( 'all' ) ) {
$title = $verified;
}
if ( is_null( $title ) ) {
- $this->error( "Something awry; empty title.", true );
+ $this->fatalError( "Something awry; empty title." );
}
$ns = $title->getNamespace();
$dest = $title->getDBkey();
$iterator = $tempRepo->getBackend()->getFileList( [ 'dir' => $dir, 'adviseStat' => 1 ] );
$this->output( "Deleting orphaned temp files...\n" );
if ( strpos( $dir, '/local-temp' ) === false ) { // sanity check
- $this->error( "Temp repo is not using the temp container.", 1 ); // die
+ $this->fatalError( "Temp repo is not using the temp container." );
}
$i = 0;
$batch = []; // operation batch
if ( $this->hasOption( 'tidy' ) ) {
global $wgUseTidy;
if ( !$wgUseTidy ) {
- $this->error( 'Tidy was requested but $wgUseTidy is not set in LocalSettings.php', true );
+ $this->fatalError( 'Tidy was requested but $wgUseTidy is not set in LocalSettings.php' );
}
$this->options->setTidy( true );
}
unset( $var );
$arg = $this->getArg( 0 );
if ( !is_file( $arg ) ) {
- $this->error( "$arg is not a file.", true );
+ $this->fatalError( "$arg is not a file." );
}
require $arg;
unset( $arg );
protected function handleExtensionFunctions( $realName, $value ) {
foreach ( $value as $func ) {
if ( $func instanceof Closure ) {
- $this->error( "Error: Closures cannot be converted to JSON. " .
- "Please move your extension function somewhere else.", 1
+ $this->fatalError( "Error: Closures cannot be converted to JSON. " .
+ "Please move your extension function somewhere else."
);
}
// check if $func exists in the global scope
if ( function_exists( $func ) ) {
- $this->error( "Error: Global functions cannot be converted to JSON. " .
- "Please move your extension function ($func) into a class.", 1
+ $this->fatalError( "Error: Global functions cannot be converted to JSON. " .
+ "Please move your extension function ($func) into a class."
);
}
}
}
foreach ( $handlers as $func ) {
if ( $func instanceof Closure ) {
- $this->error( "Error: Closures cannot be converted to JSON. " .
- "Please move the handler for $hookName somewhere else.", 1
+ $this->fatalError( "Error: Closures cannot be converted to JSON. " .
+ "Please move the handler for $hookName somewhere else."
);
}
// Check if $func exists in the global scope
if ( function_exists( $func ) ) {
- $this->error( "Error: Global functions cannot be converted to JSON. " .
- "Please move the handler for $hookName inside a class.", 1
+ $this->fatalError( "Error: Global functions cannot be converted to JSON. " .
+ "Please move the handler for $hookName inside a class."
);
}
}
'adviseStat' => true // avoid HEADs
] );
if ( $srcPathsRel === null ) {
- $this->error( "Could not list files in $container.", 1 ); // die
+ $this->fatalError( "Could not list files in $container." );
}
}
'adviseStat' => true // avoid HEADs
] );
if ( $dstPathsRel === null ) {
- $this->error( "Could not list files in $container.", 1 ); // die
+ $this->fatalError( "Could not list files in $container." );
}
$this->statCache = [];
foreach ( $dstPathsRel as $dstPathRel ) {
$srcPathsRel = $src->getFileList( [
'dir' => $src->getRootStoragePath() . "/$backendRel" ] );
if ( $srcPathsRel === null ) {
- $this->error( "Could not list files in source container.", 1 ); // die
+ $this->fatalError( "Could not list files in source container." );
}
$dstPathsRel = $dst->getFileList( [
'dir' => $dst->getRootStoragePath() . "/$backendRel" ] );
if ( $dstPathsRel === null ) {
- $this->error( "Could not list files in destination container.", 1 ); // die
+ $this->fatalError( "Could not list files in destination container." );
}
// Get the list of destination files
$relFilesDstSha1 = [];
$status = $dst->prepare( [ 'dir' => dirname( $dstPath ), 'bypassReadOnly' => 1 ] );
if ( !$status->isOK() ) {
$this->error( print_r( $status->getErrorsArray(), true ) );
- $this->error( "$wikiId: Could not copy $srcPath to $dstPath.", 1 ); // die
+ $this->fatalError( "$wikiId: Could not copy $srcPath to $dstPath." );
}
$ops[] = [ 'op' => 'store',
'src' => $fsFile->getPath(), 'dst' => $dstPath, 'overwrite' => 1 ];
$elapsed_ms = floor( ( microtime( true ) - $t_start ) * 1000 );
if ( !$status->isOK() ) {
$this->error( print_r( $status->getErrorsArray(), true ) );
- $this->error( "$wikiId: Could not copy file batch.", 1 ); // die
+ $this->fatalError( "$wikiId: Could not copy file batch." );
} elseif ( count( $copiedRel ) ) {
$this->output( "\n\tCopied these file(s) [{$elapsed_ms}ms]:\n\t" .
implode( "\n\t", $copiedRel ) . "\n\n" );
$elapsed_ms = floor( ( microtime( true ) - $t_start ) * 1000 );
if ( !$status->isOK() ) {
$this->error( print_r( $status->getErrorsArray(), true ) );
- $this->error( "$wikiId: Could not delete file batch.", 1 ); // die
+ $this->fatalError( "$wikiId: Could not delete file batch." );
} elseif ( count( $deletedRel ) ) {
$this->output( "\n\tDeleted these file(s) [{$elapsed_ms}ms]:\n\t" .
implode( "\n\t", $deletedRel ) . "\n\n" );
$dstKey = $this->getOption( 'dst' );
if ( !isset( $wgJobQueueMigrationConfig[$srcKey] ) ) {
- $this->error( "\$wgJobQueueMigrationConfig not set for '$srcKey'.", 1 );
+ $this->fatalError( "\$wgJobQueueMigrationConfig not set for '$srcKey'." );
} elseif ( !isset( $wgJobQueueMigrationConfig[$dstKey] ) ) {
- $this->error( "\$wgJobQueueMigrationConfig not set for '$dstKey'.", 1 );
+ $this->fatalError( "\$wgJobQueueMigrationConfig not set for '$dstKey'." );
}
$types = ( $this->getOption( 'type' ) === 'all' )
$user = User::newFromName( $username );
if ( !is_object( $user ) ) {
- $this->error( "invalid username.", true );
+ $this->fatalError( "invalid username." );
}
$exists = ( 0 !== $user->idForName() );
if ( $exists && !$force ) {
- $this->error( "Account exists. Perhaps you want the --force option?", true );
+ $this->fatalError( "Account exists. Perhaps you want the --force option?" );
} elseif ( !$exists && !$password ) {
- $this->error( "Argument <password> required!", false );
+ $this->error( "Argument <password> required!" );
$this->maybeHelp( true );
} elseif ( $exists ) {
$inGroups = $user->getGroups();
$user->saveSettings();
}
} catch ( PasswordError $pwe ) {
- $this->error( $pwe->getText(), true );
+ $this->fatalError( $pwe->getText() );
}
}
$outfile = $this->getArg( 1 );
if ( !is_readable( $infile ) && $infile !== 'php://stdin' ) {
- $this->error( "Cannot open input file $infile for reading", 1 );
+ $this->fatalError( "Cannot open input file $infile for reading" );
}
$file = fopen( $infile, 'r' );
if ( $file === false ) {
- $this->error( "Cannot read input file $infile", 1 );
+ $this->fatalError( "Cannot read input file $infile" );
}
try {
" (out of $i) passwords to $outfile\n"
);
} catch ( \Cdb\Exception $e ) {
- $this->error( "Error writing cdb file: " . $e->getMessage(), 2 );
+ $this->fatalError( "Error writing cdb file: " . $e->getMessage(), 2 );
}
}
}
$user = User::newFromName( $username );
}
if ( !$user ) {
- $this->error( "Invalid username", true );
+ $this->fatalError( "Invalid username" );
}
$wgUser = $user;
# Setup
if ( !$file ) {
- $this->error( "Unable to read file, exiting", true );
+ $this->fatalError( "Unable to read file, exiting" );
}
$dbw = $this->getDB( DB_MASTER );
// in order to hide it in RecentChanges.
$user = User::newFromName( 'MediaWiki default' );
if ( !$user ) {
- $this->error( "Invalid username", true );
+ $this->fatalError( "Invalid username" );
}
$user->addGroup( 'bot' );
$wgUser = $user;
$this->fetchMessageInfo( false, $messageInfo );
} else {
if ( !isset( $langCodes[$langCode] ) ) {
- $this->error( 'Invalid language code: ' . $langCode, 1 );
+ $this->fatalError( 'Invalid language code: ' . $langCode );
}
$this->fetchMessageInfo( $langCode, $messageInfo );
}
$user = User::newSystemUser( 'MediaWiki default', [ 'steal' => true ] );
if ( !$user ) {
- $this->error( "Invalid username", true );
+ $this->fatalError( "Invalid username" );
}
global $wgUser;
$wgUser = $user;
} elseif ( $this->hasOption( 'revrange' ) ) {
$this->dump( WikiExporter::RANGE, $textMode );
} else {
- $this->error( 'No valid action specified.', 1 );
+ $this->fatalError( 'No valid action specified.' );
}
}
public function execute() {
if ( !( $this->hasOption( 'file' ) ^ $this->hasOption( 'dump' ) ) ) {
- $this->error( "You must provide a file or dump", true );
+ $this->fatalError( "You must provide a file or dump" );
}
$this->checkOptions();
if ( $this->getOption( 'dump' ) == '-' ) {
$source = new ImportStreamSource( $this->getStdin() );
} else {
- $this->error( "Sorry, I don't support dump filenames yet. "
- . "Use - and provide it on stdin on the meantime.", true );
+ $this->fatalError( "Sorry, I don't support dump filenames yet. "
+ . "Use - and provide it on stdin on the meantime." );
}
$importer = new WikiImporter( $source, $this->getConfig() );
$wgUser = User::newFromName( $userName );
}
if ( !$wgUser ) {
- $this->error( "Invalid username", true );
+ $this->fatalError( "Invalid username" );
}
if ( $wgUser->isAnon() ) {
$wgUser->addToDatabase();
$title = Title::newFromText( $this->getArg() );
if ( !$title ) {
- $this->error( "Invalid title", true );
+ $this->fatalError( "Invalid title" );
}
if ( $this->hasOption( 'nocreate' ) && !$title->exists() ) {
- $this->error( "Page does not exist", true );
+ $this->fatalError( "Page does not exist" );
} elseif ( $this->hasOption( 'createonly' ) && $title->exists() ) {
- $this->error( "Page already exists", true );
+ $this->fatalError( "Page already exists" );
}
$page = WikiPage::factory( $title );
if ( $filekey === '*' ) { // all versions by name
if ( !strlen( $filename ) ) {
- $this->error( "Missing --filename parameter.", 1 );
+ $this->fatalError( "Missing --filename parameter." );
}
$afile = false;
} else { // specified version
[ 'fa_storage_group' => 'deleted', 'fa_storage_key' => $filekey ],
__METHOD__, [], $fileQuery['joins'] );
if ( !$row ) {
- $this->error( "No deleted file exists with key '$filekey'.", 1 );
+ $this->fatalError( "No deleted file exists with key '$filekey'." );
}
$filename = $row->fa_name;
$afile = ArchivedFile::newFromRow( $row );
$file = wfLocalFile( $filename );
if ( $file->exists() ) {
- $this->error( "File '$filename' is still a public file, use the delete form.\n", 1 );
+ $this->fatalError( "File '$filename' is still a public file, use the delete form.\n" );
}
$this->output( "Purging all thumbnails for file '$filename'..." );
$handle = fopen( $file, 'w' );
if ( !$handle ) {
- $this->error( "Failed to open $file for writing.\n", 1 );
+ $this->fatalError( "Failed to open $file for writing.\n" );
}
$exporter = new SiteExporter( $handle );
) {
$this->output( "Looks good!\n" );
} else {
- $this->error( 'The script finished with errors.', 1 );
+ $this->fatalError( 'The script finished with errors.' );
}
}
$repo = RepoGroup::singleton()->getLocalRepo();
if ( $repo->hasSha1Storage() ) {
- $this->error( "Local repo uses SHA-1 file storage names; aborting.", 1 );
+ $this->fatalError( "Local repo uses SHA-1 file storage names; aborting." );
}
$directory = $repo->getZonePath( 'public' );
$list = $repo->getBackend()->getFileList( [ 'dir' => $directory ] );
if ( $list === null ) {
- $this->error( "Could not get file listing.", 1 );
+ $this->fatalError( "Could not get file listing." );
}
$pathBatch = [];
if ( $this->hasOption( 'title' ) ) {
$title = Title::newFromText( $this->getOption( 'title' ) );
if ( !$title || !$title->isRedirect() ) {
- $this->error( $title->getPrefixedText() . " is not a redirect!\n", true );
+ $this->fatalError( $title->getPrefixedText() . " is not a redirect!\n" );
}
} else {
$title = null;
$row = $dbw->fetchObject( $res );
if ( is_null( $row->minrev ) ) {
- $this->error( "No revisions in search period.", true );
+ $this->fatalError( "No revisions in search period." );
}
$minRev = $row->minrev;
$numBadRevs = count( $badRevs );
if ( $numBadRevs > $numGoodRevs ) {
- $this->error(
+ $this->fatalError(
"The majority of revisions in the search interval are marked as bad.
Are you sure the offset ($offset) has the right sign? Positive means the clock
was incorrectly set forward, negative means the clock was incorrectly set back.
If the offset is right, then increase the search interval until there are enough
- good revisions to provide a majority reference.", true );
+ good revisions to provide a majority reference." );
} elseif ( $numBadRevs == 0 ) {
$this->output( "No bad revisions found.\n" );
exit( 0 );
$fileName = $this->getArg( 0 );
$inFile = fopen( $fileName, 'r' );
if ( !$inFile ) {
- $this->error( "Unable to open input file \"$fileName\"" );
- exit( 1 );
+ $this->fatalError( "Unable to open input file \"$fileName\"" );
}
} else {
$inFile = STDIN;
$fileName = $this->getOption( 'outfile' );
$outFile = fopen( $fileName, 'w' );
if ( !$outFile ) {
- $this->error( "Unable to open output file \"$fileName\"" );
- exit( 1 );
+ $this->fatalError( "Unable to open output file \"$fileName\"" );
}
} else {
$outFile = STDOUT;
if ( $extension ) {
if ( $phpfile ) {
- $this->error( "The phpfile is already specified, conflicts with --extension.", 1 );
+ $this->fatalError( "The phpfile is already specified, conflicts with --extension." );
}
$phpfile = "$IP/extensions/$extension/$extension.i18n.php";
}
$this->output( "Creating directory $jsondir.\n" );
$success = mkdir( $jsondir );
if ( !$success ) {
- $this->error( "Could not create directory $jsondir", 1 );
+ $this->fatalError( "Could not create directory $jsondir" );
}
}
if ( !is_readable( $phpfile ) ) {
- $this->error( "Error reading $phpfile", 1 );
+ $this->fatalError( "Error reading $phpfile" );
}
$messages = null;
include $phpfile;
$phpfileContents = file_get_contents( $phpfile );
if ( !isset( $messages ) ) {
- $this->error( "PHP file $phpfile does not define \$messages array", 1 );
+ $this->fatalError( "PHP file $phpfile does not define \$messages array" );
}
if ( !$messages ) {
- $this->error( "PHP file $phpfile contains an empty \$messages array. " .
- "Maybe it was already converted?", 1 );
+ $this->fatalError( "PHP file $phpfile contains an empty \$messages array. " .
+ "Maybe it was already converted?" );
}
if ( !isset( $messages['en'] ) || !is_array( $messages['en'] ) ) {
- $this->error( "PHP file $phpfile does not set language codes", 1 );
+ $this->fatalError( "PHP file $phpfile does not set language codes" );
}
foreach ( $messages as $langcode => $langmsgs ) {
FormatJson::encode( $langmsgs, "\t", FormatJson::ALL_OK ) . "\n"
);
if ( $success === false ) {
- $this->error( "FAILED to write $jsonfile", 1 );
+ $this->fatalError( "FAILED to write $jsonfile" );
}
$this->output( "$jsonfile\n" );
}
# Create directory if needed
$fspath = $this->getOption( 'fspath', getcwd() );
if ( !wfMkdirParents( $fspath, null, __METHOD__ ) ) {
- $this->error( "Can not create directory $fspath.", 1 );
+ $this->fatalError( "Can not create directory $fspath." );
}
$this->fspath = realpath( $fspath ) . DIRECTORY_SEPARATOR;
$validFormat = in_array( $format, self::$outFormats );
if ( !$validFormat ) {
- $this->error( "--format set to an unrecognized format", 0 );
+ $this->error( "--format set to an unrecognized format" );
$error_out = true;
}
$titleText = $this->getArg( 0 );
$title = Title::newFromText( $titleText );
if ( !$title ) {
- $this->error( "$titleText is not a valid title.\n", true );
+ $this->fatalError( "$titleText is not a valid title.\n" );
}
$rev = Revision::newFromTitle( $title );
if ( !$rev ) {
$titleText = $title->getPrefixedText();
- $this->error( "Page $titleText does not exist.\n", true );
+ $this->fatalError( "Page $titleText does not exist.\n" );
}
$content = $rev->getContent( $this->hasOption( 'show-private' )
? Revision::RAW
if ( $content === false ) {
$titleText = $title->getPrefixedText();
- $this->error( "Couldn't extract the text from $titleText.\n", true );
+ $this->fatalError( "Couldn't extract the text from $titleText.\n" );
}
$this->output( $content->serialize() );
}
$tmpDir = wfTempDir() . '/mw-make-repo' . mt_rand( 0, 1 << 31 );
if ( !mkdir( $tmpDir ) ) {
- $this->error( 'Unable to create temporary directory', 1 );
+ $this->fatalError( 'Unable to create temporary directory' );
}
file_put_contents( "$tmpDir/file-list", implode( "\n", $files ) );
passthru( $cmd, $ret );
if ( $ret ) {
$this->cleanupTemp( $tmpDir );
- $this->error( "Error: HHVM returned error code $ret", 1 );
+ $this->fatalError( "Error: HHVM returned error code $ret" );
}
if ( !rename( "$tmpDir/hhvm.hhbc", $this->getOption( 'output' ) ) ) {
$this->cleanupTemp( $tmpDir );
- $this->error( "Error: unable to rename output file", 1 );
+ $this->fatalError( "Error: unable to rename output file" );
}
$this->cleanupTemp( $tmpDir );
return 0;
public function execute() {
if ( wfReadOnly() ) {
- $this->error( "Wiki is in read-only mode; you'll need to disable it for import to work.", true );
+ $this->fatalError( "Wiki is in read-only mode; you'll need to disable it for import to work." );
}
$this->reportingInterval = intval( $this->getOption( 'report', 100 ) );
if ( strval( $ns ) === $namespace && $wgContLang->getNsText( $ns ) !== false ) {
return $ns;
}
- $this->error( "Unknown namespace text / index specified: $namespace", true );
+ $this->fatalError( "Unknown namespace text / index specified: $namespace" );
}
/**
$statusRootPage = $importer->setTargetRootPage( $this->getOption( 'rootpage' ) );
if ( !$statusRootPage->isGood() ) {
// Die here so that it doesn't print "Done!"
- $this->error( $statusRootPage->getMessage()->text(), 1 );
+ $this->fatalError( $statusRootPage->getMessage()->text() );
return false;
}
}
# Check Protection
if ( $this->hasOption( 'protect' ) && $this->hasOption( 'unprotect' ) ) {
- $this->error( "Cannot specify both protect and unprotect. Only 1 is allowed.\n", 1 );
+ $this->fatalError( "Cannot specify both protect and unprotect. Only 1 is allowed.\n" );
}
if ( $this->hasOption( 'protect' ) && trim( $this->getOption( 'protect' ) ) ) {
- $this->error( "You must specify a protection option.\n", 1 );
+ $this->fatalError( "You must specify a protection option.\n" );
}
# Prepare the list of allowed extensions
if ( $commentFile !== null ) {
$comment = file_get_contents( $commentFile );
if ( $comment === false || $comment === null ) {
- $this->error( "failed to read comment file: {$commentFile}\n", 1 );
+ $this->fatalError( "failed to read comment file: {$commentFile}\n" );
}
} else {
$comment = $this->getOption( 'comment', 'Importing file' );
$files[$filename] = file_get_contents( $filename );
}
if ( !$found ) {
- $this->error( "Fatal error: The file '$arg' does not exist!", 1 );
+ $this->fatalError( "Fatal error: The file '$arg' does not exist!" );
}
}
};
}
if ( !$user ) {
- $this->error( "Invalid username\n", true );
+ $this->fatalError( "Invalid username\n" );
}
if ( $user->isAnon() ) {
$user->addToDatabase();
$this->output( "Done! $successCount succeeded, $skipCount skipped.\n" );
if ( $exit ) {
- $this->error( "Import failed with $failCount failed pages.\n", $exit );
+ $this->fatalError( "Import failed with $failCount failed pages.\n", $exit );
}
}
}
$dbpass = file_get_contents( $dbpassfile ); // returns false on failure
MediaWiki\restoreWarnings();
if ( $dbpass === false ) {
- $this->error( "Couldn't open $dbpassfile", true );
+ $this->fatalError( "Couldn't open $dbpassfile" );
}
$this->mOptions['dbpass'] = trim( $dbpass, "\r\n" );
}
$pass = file_get_contents( $passfile ); // returns false on failure
MediaWiki\restoreWarnings();
if ( $pass === false ) {
- $this->error( "Couldn't open $passfile", true );
+ $this->fatalError( "Couldn't open $passfile" );
}
$this->mOptions['pass'] = trim( $pass, "\r\n" );
} elseif ( $this->getOption( 'pass' ) === null ) {
- $this->error( 'You need to provide the option "pass" or "passfile"', true );
+ $this->fatalError( 'You need to provide the option "pass" or "passfile"' );
}
}
$file = $this->getOption( 'file' );
if ( $username === null && $file === null ) {
- $this->error( 'Either --user or --file is required', 1 );
+ $this->fatalError( 'Either --user or --file is required' );
} elseif ( $username !== null && $file !== null ) {
- $this->error( 'Cannot use both --user and --file', 1 );
+ $this->fatalError( 'Cannot use both --user and --file' );
}
if ( $username !== null ) {
$usernames = is_readable( $file ) ?
file( $file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ) : false;
if ( $usernames === false ) {
- $this->error( "Could not open $file", 2 );
+ $this->fatalError( "Could not open $file", 2 );
}
}
$error .= "* $ucdallURL\n";
}
- $this->error( $error );
- exit( 1 );
+ $this->fatalError( $error );
}
$debugOutFileName = $this->getOption( 'debug-output' );
if ( $debugOutFileName ) {
$this->debugOutFile = fopen( $debugOutFileName, 'w' );
if ( !$this->debugOutFile ) {
- $this->error( "Unable to open debug output file for writing" );
- exit( 1 );
+ $this->fatalError( "Unable to open debug output file for writing" );
}
}
$this->loadUcd();
function generateFirstChars() {
$file = fopen( "{$this->dataDir}/allkeys.txt", 'r' );
if ( !$file ) {
- $this->error( "Unable to open allkeys.txt" );
- exit( 1 );
+ $this->fatalError( "Unable to open allkeys.txt" );
}
global $IP;
$outFile = fopen( "$IP/serialized/first-letters-root.ser", 'w' );
if ( !$outFile ) {
- $this->error( "Unable to open output file first-letters-root.ser" );
- exit( 1 );
+ $this->fatalError( "Unable to open output file first-letters-root.ser" );
}
$goodTertiaryChars = [];
if ( !$this->hasOption( 'unicode-data-file' ) ) {
$dataFile = 'UnicodeData.txt';
if ( !file_exists( $dataFile ) ) {
- $this->error( "Unable to find UnicodeData.txt. Please specify " .
+ $this->fatalError( "Unable to find UnicodeData.txt. Please specify " .
"its location with --unicode-data-file=<FILE>" );
- exit( 1 );
}
} else {
$dataFile = $this->getOption( 'unicode-data-file' );
if ( !file_exists( $dataFile ) ) {
- $this->error( 'Unable to find the specified data file.' );
- exit( 1 );
+ $this->fatalError( 'Unable to find the specified data file.' );
}
}
$file = fopen( $dataFile, 'r' );
if ( !$file ) {
- $this->error( 'Unable to open the data file.' );
- exit( 1 );
+ $this->fatalError( 'Unable to open the data file.' );
}
// For the file format, see http://www.unicode.org/reports/tr44/
public function execute() {
if ( !function_exists( 'memory_get_usage' ) ) {
- $this->error( "You must compile PHP with --enable-memory-limit", true );
+ $this->fatalError( "You must compile PHP with --enable-memory-limit" );
}
$langtool = new Languages();
public function execute() {
$user = User::newFromName( $this->getOption( 'user' ) );
if ( !$user->getId() ) {
- $this->error( "No such user exists.", 1 );
+ $this->fatalError( "No such user exists." );
}
$count = $this->getOption( 'count' );
} elseif ( $action === 'repush-abandoned' ) {
$this->repushAbandoned( $queue );
} else {
- $this->error( "Invalid action '$action'.", 1 );
+ $this->fatalError( "Invalid action '$action'." );
}
}
$iterations = $this->getOption( 'i', 100 );
if ( $cache ) {
if ( !isset( $wgObjectCaches[$cache] ) ) {
- $this->error( "MediaWiki isn't configured with a cache named '$cache'", 1 );
+ $this->fatalError( "MediaWiki isn't configured with a cache named '$cache'" );
}
$servers = $wgObjectCaches[$cache]['servers'];
} elseif ( $this->hasArg() ) {
} elseif ( isset( $wgObjectCaches[$wgMainCacheType]['servers'] ) ) {
$servers = $wgObjectCaches[$wgMainCacheType]['servers'];
} else {
- $this->error( "MediaWiki isn't configured for Memcached usage", 1 );
+ $this->fatalError( "MediaWiki isn't configured for Memcached usage" );
}
# find out the longest server string to nicely align output later on
&& !$this->hasOption( 'list-file' )
&& !$this->hasOption( 'extensions-dir' )
) {
- $this->error( "Either --list-file or --extensions-dir must be provided if " .
- "\$wgExtensionEntryPointListFiles is not set", 1 );
+ $this->fatalError( "Either --list-file or --extensions-dir must be provided if " .
+ "\$wgExtensionEntryPointListFiles is not set" );
}
$mmfl = [ 'setupFiles' => [] ];
public function execute() {
$oldLayout = $this->getOption( 'oldlayout' );
if ( !in_array( $oldLayout, [ 'name', 'sha1' ] ) ) {
- $this->error( "Invalid old layout.", 1 );
+ $this->fatalError( "Invalid old layout." );
}
$newLayout = $this->getOption( 'newlayout' );
if ( !in_array( $newLayout, [ 'name', 'sha1' ] ) ) {
- $this->error( "Invalid new layout.", 1 );
+ $this->fatalError( "Invalid new layout." );
}
$since = $this->getOption( 'since' );
$end = $dbw->selectField( 'user_groups', 'MAX(ug_user)',
[ 'ug_group' => $oldGroup ], __FUNCTION__ );
if ( $start === null ) {
- $this->error( "Nothing to do - no users in the '$oldGroup' group", true );
+ $this->fatalError( "Nothing to do - no users in the '$oldGroup' group" );
}
# Do remaining chunk
$end += $batchSize - 1;
public function execute() {
if ( !count( $this->mArgs ) ) {
- $this->error( "minify.php: At least one input file must be specified." );
- exit( 1 );
+ $this->fatalError( "minify.php: At least one input file must be specified." );
}
if ( $this->hasOption( 'outfile' ) ) {
if ( count( $this->mArgs ) > 1 ) {
- $this->error( '--outfile may only be used with a single input file.' );
- exit( 1 );
+ $this->fatalError( '--outfile may only be used with a single input file.' );
}
// Minify one file
}
if ( !file_exists( $inPath ) ) {
- $this->error( "File does not exist: $arg", true );
+ $this->fatalError( "File does not exist: $arg" );
}
$extension = $this->getExtension( $inName );
public function getExtension( $fileName ) {
$dotPos = strrpos( $fileName, '.' );
if ( $dotPos === false ) {
- $this->error( "No file extension, cannot determine type: $fileName" );
- exit( 1 );
+ $this->fatalError( "No file extension, cannot determine type: $fileName" );
}
return substr( $fileName, $dotPos + 1 );
$inText = file_get_contents( $inPath );
if ( $inText === false ) {
- $this->error( "Unable to open file $inPath for reading." );
- exit( 1 );
+ $this->fatalError( "Unable to open file $inPath for reading." );
}
$outFile = fopen( $outPath, 'w' );
if ( !$outFile ) {
- $this->error( "Unable to open file $outPath for writing." );
- exit( 1 );
+ $this->fatalError( "Unable to open file $outPath for writing." );
}
switch ( $extension ) {
# Setup
if ( !$file ) {
- $this->error( "Unable to read file, exiting", true );
+ $this->fatalError( "Unable to read file, exiting" );
}
if ( $user === false ) {
$wgUser = User::newSystemUser( 'Move page script', [ 'steal' => true ] );
$wgUser = User::newFromName( $user );
}
if ( !$wgUser ) {
- $this->error( "Invalid username", true );
+ $this->fatalError( "Invalid username" );
}
# Setup complete, now start
$tmpFile = tempnam( wfTempDir(), 'MWDocGen-' );
if ( file_put_contents( $tmpFile, $conf ) === false ) {
- $this->error( "Could not write doxygen configuration to file $tmpFile\n",
- /** exit code: */ 1 );
+ $this->fatalError( "Could not write doxygen configuration to file $tmpFile\n" );
}
$command = $this->doxygen . ' ' . $tmpFile;
);
if ( $exitcode !== 0 ) {
- $this->error( "Something went wrong (exit: $exitcode)\n",
- $exitcode );
+ $this->fatalError( "Something went wrong (exit: $exitcode)\n", $exitcode );
}
}
}
$code = 1;
}
$this->output( $text );
- $this->error( '', $code );
+ exit( $code );
}
}
$ns = $this->getOption( 'ns' );
if ( !ctype_digit( $ns ) && $ns !== 'all' ) {
- $this->error( 'Invalid namespace', 1 );
+ $this->fatalError( 'Invalid namespace' );
}
$ns = $ns === 'all' ? 'all' : (int)$ns;
$table = $this->getOption( 'table' );
$this->populatePage( $dbw, $ns );
break;
default:
- $this->error( "Invalid table name: $table", 1 );
+ $this->fatalError( "Invalid table name: $table" );
}
}
__METHOD__
);
if ( !$res ) {
- $this->error( "No such file: $file", true );
-
- return false;
+ $this->fatalError( "No such file: $file" );
}
$this->output( "Populating img_sha1 field for specified files\n" );
} else {
public function doDBUpdates() {
$dbw = $this->getDB( DB_MASTER );
if ( !$dbw->tableExists( 'revision' ) ) {
- $this->error( "revision table does not exist", true );
+ $this->fatalError( "revision table does not exist" );
} elseif ( !$dbw->tableExists( 'archive' ) ) {
- $this->error( "archive table does not exist", true );
+ $this->fatalError( "archive table does not exist" );
} elseif ( !$dbw->fieldExists( 'revision', 'rev_len', __METHOD__ ) ) {
$this->output( "rev_len column does not exist\n\n", true );
$db = $this->getDB( DB_MASTER );
if ( !$db->tableExists( 'revision' ) ) {
- $this->error( "revision table does not exist", true );
+ $this->fatalError( "revision table does not exist" );
} elseif ( !$db->tableExists( 'archive' ) ) {
- $this->error( "archive table does not exist", true );
+ $this->fatalError( "archive table does not exist" );
} elseif ( !$db->fieldExists( 'revision', 'rev_sha1', __METHOD__ ) ) {
$this->output( "rev_sha1 column does not exist\n\n", true );
$user = User::newFromName( $userName );
}
if ( !$user ) {
- $this->error( "Invalid username", true );
+ $this->fatalError( "Invalid username" );
}
// @todo FIXME: This is reset 7 lines down.
$t = Title::newFromText( $this->getArg() );
if ( !$t ) {
- $this->error( "Invalid title", true );
+ $this->fatalError( "Invalid title" );
}
$restrictions = [];
global $wgUseFileCache, $wgFileCacheDirectory;
if ( !$wgUseFileCache ) {
- $this->error( "Nothing to do -- \$wgUseFileCache is disabled.", true );
+ $this->fatalError( "Nothing to do -- \$wgUseFileCache is disabled." );
}
$age = $this->getOption( 'agedays' );
if ( !ctype_digit( $age ) ) {
- $this->error( "Non-integer 'age' parameter given.", true );
+ $this->fatalError( "Non-integer 'age' parameter given." );
}
// Delete items with a TS older than this
$this->minSurviveTimestamp = time() - ( 86400 * $age );
$dir = $wgFileCacheDirectory;
if ( !is_dir( $dir ) ) {
- $this->error( "Nothing to do -- \$wgFileCacheDirectory directory not found.", true );
+ $this->fatalError( "Nothing to do -- \$wgFileCacheDirectory directory not found." );
}
$subDir = $this->getOption( 'subdir' );
if ( $subDir !== null ) {
if ( !is_dir( "$dir/$subDir" ) ) {
- $this->error( "The specified subdirectory `$subDir` does not exist.", true );
+ $this->fatalError( "The specified subdirectory `$subDir` does not exist." );
}
$this->output( "Pruning `$dir/$subDir` directory...\n" );
$this->prune_directory( "$dir/$subDir", 'report' );
} elseif ( $inputAge !== null ) {
$date = wfTimestamp( TS_MW, time() + $wgParserCacheExpireTime - intval( $inputAge ) );
} else {
- $this->error( "Must specify either --expiredate or --age", 1 );
+ $this->fatalError( "Must specify either --expiredate or --age" );
return;
}
$this->usleep = 1e3 * $this->getOption( 'msleep', 0 );
$pc = MediaWikiServices::getInstance()->getParserCache()->getCacheStorage();
$success = $pc->deleteObjectsExpiringBefore( $date, [ $this, 'showProgressAndWait' ] );
if ( !$success ) {
- $this->error( "\nCannot purge this kind of parser cache.", 1 );
+ $this->fatalError( "\nCannot purge this kind of parser cache." );
}
$this->showProgressAndWait( 100 );
$this->output( "\nDone\n" );
} else {
$user = User::newFromName( $username );
if ( !$user ) {
- $this->error( "Invalid username", true );
+ $this->fatalError( "Invalid username" );
}
}
$user->load();
global $wgRequestTime;
if ( !$this->enabled ) {
- $this->error( "Nothing to do -- \$wgUseFileCache is disabled.", true );
+ $this->fatalError( "Nothing to do -- \$wgUseFileCache is disabled." );
}
$start = $this->getOption( 'start', "0" );
if ( !ctype_digit( $start ) ) {
- $this->error( "Invalid value for start parameter.", true );
+ $this->fatalError( "Invalid value for start parameter." );
}
$start = intval( $start );
$end = $this->getOption( 'end', "0" );
if ( !ctype_digit( $end ) ) {
- $this->error( "Invalid value for end parameter.", true );
+ $this->fatalError( "Invalid value for end parameter." );
}
$end = intval( $end );
? $end
: $dbr->selectField( 'page', 'MAX(page_id)', false, __METHOD__ );
if ( !$start ) {
- $this->error( "Nothing to do.", true );
+ $this->fatalError( "Nothing to do." );
}
$_SERVER['HTTP_ACCEPT_ENCODING'] = 'bgzip'; // hack, no real client
explode( ',', $this->getOption( 'lang' ) ) );
# Bailed out if nothing is left
if ( count( $codes ) == 0 ) {
- $this->error( 'None of the languages specified exists.', 1 );
+ $this->fatalError( 'None of the languages specified exists.' );
}
} else {
# By default get all languages
$jsonFile = $this->getConfig()->get( 'SitesCacheFile' );
if ( $jsonFile === false ) {
- $this->error( 'Error: No file set in configuration for SitesCacheFile.', 1 );
+ $this->fatalError( 'Error: No file set in configuration for SitesCacheFile.' );
}
}
( $this->hasOption( 'from' ) && !$this->hasOption( 'to' ) ) ||
( !$this->hasOption( 'from' ) && $this->hasOption( 'to' ) )
) {
- $this->error( "Both 'from' and 'to' must be given, or neither", 1 );
+ $this->fatalError( "Both 'from' and 'to' must be given, or neither" );
}
$this->rebuildRecentChangesTablePass1();
// Shouldn't be needed for Postgres
$this->db = $this->getDB( DB_MASTER );
if ( $this->db->getType() == 'postgres' ) {
- $this->error( "This script is not needed when using Postgres.\n", true );
+ $this->fatalError( "This script is not needed when using Postgres.\n" );
}
if ( $this->db->getType() == 'sqlite' ) {
if ( !DatabaseSqlite::getFulltextSearchModule() ) {
- $this->error( "Your version of SQLite module for PHP doesn't "
- . "support full-text search (FTS3).\n", true );
+ $this->fatalError( "Your version of SQLite module for PHP doesn't "
+ . "support full-text search (FTS3).\n" );
}
if ( !$this->db->checkForEnabledSearch() ) {
- $this->error( "Your database schema is not configured for "
- . "full-text search support. Run update.php.\n", true );
+ $this->fatalError( "Your database schema is not configured for "
+ . "full-text search support. Run update.php.\n" );
}
}
public function execute() {
$this->mode = $this->getOption( 'mode' );
if ( !in_array( $this->mode, [ 'pages', 'subcats', 'files' ] ) ) {
- $this->error( 'Please specify a valid mode: one of "pages", "subcats" or "files".', 1 );
+ $this->fatalError( 'Please specify a valid mode: one of "pages", "subcats" or "files".' );
}
$this->minimumId = intval( $this->getOption( 'begin', 0 ) );
$dbw = $this->getDB( DB_MASTER );
$batchSize = $this->getBatchSize();
if ( $batchSize <= 0 ) {
- $this->error( "Batch size is too low...", 12 );
+ $this->fatalError( "Batch size is too low...", 12 );
}
$repo = RepoGroup::singleton()->getLocalRepo();
}
if ( $brokenOnly && $force ) {
- $this->error( 'Cannot use --broken-only and --force together. ', 2 );
+ $this->fatalError( 'Cannot use --broken-only and --force together. ', 2 );
}
}
}
if ( ( $category = $this->getOption( 'category', false ) ) !== false ) {
$title = Title::makeTitleSafe( NS_CATEGORY, $category );
if ( !$title ) {
- $this->error( "'$category' is an invalid category name!\n", true );
+ $this->fatalError( "'$category' is an invalid category name!\n" );
}
$this->refreshCategory( $title );
} elseif ( ( $category = $this->getOption( 'tracking-category', false ) ) !== false ) {
if ( isset( $cats[$categoryKey] ) ) {
return $cats[$categoryKey]['cats'];
}
- $this->error( "Unknown tracking category {$categoryKey}\n", true );
+ $this->fatalError( "Unknown tracking category {$categoryKey}\n" );
}
}
}
$touched = $this->getOption( 'ignore-touched', "1" );
if ( !ctype_digit( $touched ) ) {
- $this->error( "Please put a valid positive integer on the --ignore-touched parameter.", true );
+ $this->fatalError( "Please put a valid positive integer on the --ignore-touched parameter." );
}
$touchedSeconds = 86400 * $touched;
foreach ( $res as $row ) {
}
if ( $old === false || $new === false ) {
- $this->error( "Invalid prefix!", true );
+ $this->fatalError( "Invalid prefix!" );
}
if ( $old === $new ) {
$this->output( "Same prefix. Nothing to rename!\n", true );
$user = User::newFromName( $userName );
}
if ( !$user || !$user->getId() || !$user->loadFromId() ) {
- $this->error( "Error: user '$userName' does not exist\n", 1 );
+ $this->fatalError( "Error: user '$userName' does not exist\n" );
}
$email = $this->getArg( 1 );
if ( !Sanitizer::validateEmail( $email ) ) {
- $this->error( "Error: email '$email' is not valid\n", 1 );
+ $this->fatalError( "Error: email '$email' is not valid\n" );
}
// Code from https://wikitech.wikimedia.org/wiki/Password_reset
$user = $this->getOption( 'user' );
$username = User::isIP( $user ) ? $user : User::getCanonicalName( $user );
if ( !$username ) {
- $this->error( 'Invalid username', true );
+ $this->fatalError( 'Invalid username' );
}
$bot = $this->hasOption( 'bot' );
if ( $this->hasOption( 'procs' ) ) {
$procs = intval( $this->getOption( 'procs' ) );
if ( $procs < 1 || $procs > 1000 ) {
- $this->error( "Invalid argument to --procs", true );
+ $this->fatalError( "Invalid argument to --procs" );
} elseif ( $procs != 1 ) {
$fc = new ForkController( $procs );
if ( $fc->start() != 'child' ) {
public function execute() {
if ( !class_exists( \Psy\Shell::class ) ) {
- $this->error( 'PsySH not found. Please run composer with the --dev option.', 1 );
+ $this->fatalError( 'PsySH not found. Please run composer with the --dev option.' );
}
$traverser = new \PhpParser\NodeTraverser();
}
}
if ( $index === null ) {
- $this->error( "No replica DB server configured with the name '$replicaDB'.", 1 );
+ $this->fatalError( "No replica DB server configured with the name '$replicaDB'." );
}
} else {
$index = DB_MASTER;
/** @var IDatabase $db DB handle for the appropriate cluster/wiki */
$db = $lb->getConnection( $index, [], $wiki );
if ( $replicaDB != '' && $db->getLBInfo( 'master' ) !== null ) {
- $this->error( "The server selected ({$db->getServer()}) is not a replica DB.", 1 );
+ $this->fatalError( "The server selected ({$db->getServer()}) is not a replica DB." );
}
if ( $index === DB_MASTER ) {
if ( $this->hasArg( 0 ) ) {
$file = fopen( $this->getArg( 0 ), 'r' );
if ( !$file ) {
- $this->error( "Unable to open input file", true );
+ $this->fatalError( "Unable to open input file" );
}
$error = $db->sourceStream( $file, null, [ $this, 'sqlPrintResult' ] );
if ( $error !== true ) {
- $this->error( $error, true );
+ $this->fatalError( $error );
} else {
exit( 0 );
}
$res = $db->query( $line );
$this->sqlPrintResult( $res, $db );
} catch ( DBQueryError $e ) {
- $this->error( $e, $dieOnError );
+ if ( $dieOnError ) {
+ $this->fatalError( $e );
+ } else {
+ $this->error( $e );
+ }
}
}
private function vacuum() {
$prevSize = filesize( $this->db->getDbFilePath() );
if ( $prevSize == 0 ) {
- $this->error( "Can't vacuum an empty database.\n", true );
+ $this->fatalError( "Can't vacuum an empty database.\n" );
}
$this->output( 'VACUUM: ' );
// This is a known bug from 2004
// It's safe to just erase the old_flags field
if ( $fix ) {
- $this->error( 'fixed', "Warning: old_flags set to 0", $id );
+ $this->addError( 'fixed', "Warning: old_flags set to 0", $id );
$dbw = wfGetDB( DB_MASTER );
$dbw->ping();
$dbw->update( 'text', [ 'old_flags' => '' ],
[ 'old_id' => $id ], __METHOD__ );
echo "Fixed\n";
} else {
- $this->error( 'fixable', "Warning: old_flags set to 0", $id );
+ $this->addError( 'fixable', "Warning: old_flags set to 0", $id );
}
} elseif ( count( array_diff( $flagArray, $knownFlags ) ) ) {
- $this->error( 'unfixable', "Error: invalid flags field \"$flags\"", $id );
+ $this->addError( 'unfixable', "Error: invalid flags field \"$flags\"", $id );
}
}
$dbr->freeResult( $res );
// Output errors for any missing text rows
foreach ( $missingTextRows as $oldId => $revId ) {
- $this->error( 'restore revision', "Error: missing text row", $oldId );
+ $this->addError( 'restore revision', "Error: missing text row", $oldId );
}
// Verify external revisions
foreach ( $res as $row ) {
$urlParts = explode( '://', $row->old_text, 2 );
if ( count( $urlParts ) !== 2 || $urlParts[1] == '' ) {
- $this->error( 'restore text', "Error: invalid URL \"{$row->old_text}\"", $row->old_id );
+ $this->addError( 'restore text', "Error: invalid URL \"{$row->old_text}\"", $row->old_id );
continue;
}
list( $proto, ) = $urlParts;
if ( $proto != 'DB' ) {
- $this->error( 'restore text', "Error: invalid external protocol \"$proto\"", $row->old_id );
+ $this->addError(
+ 'restore text',
+ "Error: invalid external protocol \"$proto\"",
+ $row->old_id );
continue;
}
$path = explode( '/', $row->old_text );
$extDb->freeResult( $res );
// Print errors for missing blobs rows
foreach ( $xBlobIds as $blobId => $oldId ) {
- $this->error( 'restore text', "Error: missing target $blobId for one-part ES URL", $oldId );
+ $this->addError(
+ 'restore text',
+ "Error: missing target $blobId for one-part ES URL",
+ $oldId );
}
}
}
$oldId = $row->old_id;
$matches = [];
if ( !preg_match( '/^O:(\d+):"(\w+)"/', $row->header, $matches ) ) {
- $this->error( 'restore text', "Error: invalid object header", $oldId );
+ $this->addError( 'restore text', "Error: invalid object header", $oldId );
continue;
}
$className = strtolower( $matches[2] );
if ( strlen( $className ) != $matches[1] ) {
- $this->error(
+ $this->addError(
'restore text',
"Error: invalid object header, wrong class name length",
$oldId
case 'historyblobstub':
case 'historyblobcurstub':
if ( strlen( $row->header ) == $headerLength ) {
- $this->error( 'unfixable', "Error: overlong stub header", $oldId );
+ $this->addError( 'unfixable', "Error: overlong stub header", $oldId );
continue;
}
$stubObj = unserialize( $row->header );
if ( !is_object( $stubObj ) ) {
- $this->error( 'restore text', "Error: unable to unserialize stub object", $oldId );
+ $this->addError( 'restore text', "Error: unable to unserialize stub object", $oldId );
continue;
}
if ( $className == 'historyblobstub' ) {
}
break;
default:
- $this->error( 'unfixable', "Error: unrecognised object class \"$className\"", $oldId );
+ $this->addError( 'unfixable', "Error: unrecognised object class \"$className\"", $oldId );
}
}
$dbr->freeResult( $res );
if ( in_array( 'object', $flags ) ) {
$urlParts = explode( '/', $row->header );
if ( $urlParts[0] != 'DB:' ) {
- $this->error(
+ $this->addError(
'unfixable',
"Error: unrecognised external storage type \"{$urlParts[0]}",
$row->old_id
);
}
} else {
- $this->error(
+ $this->addError(
'unfixable',
"Error: invalid flags \"{$row->old_flags}\" on concat bulk row {$row->old_id}",
$concatBlobs[$row->old_id] );
substr( $row->header, 0, strlen( self::CONCAT_HEADER ) ),
self::CONCAT_HEADER
) ) {
- $this->error(
+ $this->addError(
'restore text',
"Error: Incorrect object header for concat bulk row {$row->old_id}",
$concatBlobs[$row->old_id]
}
}
- function error( $type, $msg, $ids ) {
+ function addError( $type, $msg, $ids ) {
if ( is_array( $ids ) && count( $ids ) == 1 ) {
$ids = reset( $ids );
}
[ 'blob_id IN( ' . implode( ',', $blobIds ) . ')' ], __METHOD__ );
foreach ( $res as $row ) {
if ( strcasecmp( $row->header, self::CONCAT_HEADER ) ) {
- $this->error(
+ $this->addError(
'restore text',
"Error: invalid header on target $cluster/{$row->blob_id} of two-part ES URL",
$oldIds[$row->blob_id]
// Print errors for missing blobs rows
foreach ( $oldIds as $blobId => $oldIds2 ) {
- $this->error(
+ $this->addError(
'restore text',
"Error: missing target $cluster/$blobId for two-part ES URL",
$oldIds2
public function execute() {
global $wgDBname;
if ( !function_exists( "gzdeflate" ) ) {
- $this->error( "You must enable zlib support in PHP to compress old revisions!\n" .
- "Please see http://www.php.net/manual/en/ref.zlib.php\n", true );
+ $this->fatalError( "You must enable zlib support in PHP to compress old revisions!\n" .
+ "Please see http://www.php.net/manual/en/ref.zlib.php\n" );
}
$type = $this->getOption( 'type', 'concat' );
[ 'old_id=rev_text_id', 'rev_id' => $this->getArg() ]
);
if ( !$row ) {
- $this->error( "Row not found", true );
+ $this->fatalError( "Row not found" );
}
$flags = explode( ',', $row->old_flags );
public function execute() {
$dbr = $this->getDB( DB_REPLICA );
if ( !$dbr->tableExists( 'blob_orphans' ) ) {
- $this->error( "blob_orphans doesn't seem to exist, need to run trackBlobs.php first", true );
+ $this->fatalError( "blob_orphans doesn't seem to exist, need to run trackBlobs.php first" );
}
$res = $dbr->select( 'blob_orphans', '*', false, __METHOD__ );
if ( $this->hasOption( 'posdump' ) ) {
// Just dump the current position into the specified position dir
if ( !$this->hasOption( 'posdir' ) ) {
- $this->error( "Param posdir required!", 1 );
+ $this->fatalError( "Param posdir required!" );
}
if ( $this->hasOption( 'postime' ) ) {
$id = (int)$src->getJournal()->getPositionAtTime( $this->getOption( 'postime' ) );
}
if ( !$this->hasOption( 'dst' ) ) {
- $this->error( "Param dst required!", 1 );
+ $this->fatalError( "Param dst required!" );
}
$dst = FileBackendGroup::singleton()->get( $this->getOption( 'dst' ) );
$first = true; // first batch
if ( $start > $end ) { // sanity
- $this->error( "Error: given starting ID greater than ending ID.", 1 );
+ $this->fatalError( "Error: given starting ID greater than ending ID." );
}
$next = null;
$title = Title::newFromText( $pageName );
if ( !$title ) {
- $this->error( "Invalid title", true );
+ $this->fatalError( "Invalid title" );
}
if ( $user === false ) {
$wgUser = User::newSystemUser( 'Command line script', [ 'steal' => true ] );
$wgUser = User::newFromName( $user );
}
if ( !$wgUser ) {
- $this->error( "Invalid username", true );
+ $this->fatalError( "Invalid username" );
}
$archive = new PageArchive( $title, RequestContext::getMain()->getConfig() );
$this->output( "Undeleting " . $title->getPrefixedDBkey() . '...' );
list( $pcreVersion ) = explode( ' ', PCRE_VERSION, 2 );
if ( version_compare( $pcreVersion, $minimumPcreVersion, '<' ) ) {
- $this->error(
+ $this->fatalError(
"PCRE $minimumPcreVersion or later is required.\n" .
"Your PHP binary is linked with PCRE $pcreVersion.\n\n" .
"More information:\n" .
"https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE\n\n" .
- "ABORTING.\n",
- true );
+ "ABORTING.\n" );
}
$test = new PhpXmlBugTester();
if ( !$test->ok ) {
- $this->error(
+ $this->fatalError(
"Your system has a combination of PHP and libxml2 versions that is buggy\n" .
"and can cause hidden data corruption in MediaWiki and other web apps.\n" .
"Upgrade to libxml2 2.7.3 or later.\n" .
- "ABORTING (see https://bugs.php.net/bug.php?id=45996).\n",
- true );
+ "ABORTING (see https://bugs.php.net/bug.php?id=45996).\n" );
}
}
|| $this->hasOption( 'schema' )
|| $this->hasOption( 'noschema' ) )
) {
- $this->error( "Do not run update.php on this wiki. If you're seeing this you should\n"
+ $this->fatalError( "Do not run update.php on this wiki. If you're seeing this you should\n"
. "probably ask for some help in performing your schema updates or use\n"
. "the --noschema and --schema options to get an SQL file for someone\n"
. "else to inspect and run.\n\n"
- . "If you know what you are doing, you can continue with --force\n", true );
+ . "If you know what you are doing, you can continue with --force\n" );
}
$this->fileHandle = null;
if ( substr( $this->getOption( 'schema' ), 0, 2 ) === "--" ) {
- $this->error( "The --schema option requires a file as an argument.\n", true );
+ $this->fatalError( "The --schema option requires a file as an argument.\n" );
} elseif ( $this->hasOption( 'schema' ) ) {
$file = $this->getOption( 'schema' );
$this->fileHandle = fopen( $file, "w" );
if ( $this->fileHandle === false ) {
$err = error_get_last();
- $this->error( "Problem opening the schema file for writing: $file\n\t{$err['message']}", true );
+ $this->fatalError( "Problem opening the schema file for writing: $file\n\t{$err['message']}" );
}
}
if ( !$status->isOK() ) {
// This might output some wikitext like <strong> but it should be comprehensible
$text = $status->getWikiText();
- $this->error( $text, 1 );
+ $this->fatalError( $text );
}
$this->output( "Going to run database updates for " . wfWikiID() . "\n" );
$dbw = $this->getDB( DB_MASTER );
if ( $dbw->getType() !== 'mysql' ) {
- $this->error( "This change is only needed on MySQL, quitting.\n", true );
+ $this->fatalError( "This change is only needed on MySQL, quitting.\n" );
}
$res = $this->findRows( $dbw );
public function execute() {
$filename = $this->getArg( 0 );
if ( !is_readable( $filename ) ) {
- $this->error( "Error: Unable to read $filename", 1 );
+ $this->fatalError( "Error: Unable to read $filename" );
}
$json = FormatJson::decode( file_get_contents( $filename ), true );
if ( $json === null ) {
- $this->error( "Error: Invalid JSON", 1 );
+ $this->fatalError( "Error: Invalid JSON" );
}
if ( !isset( $json['manifest_version'] ) ) {
$db = $this->getDB( DB_MASTER );
$batchSize = $this->getBatchSize();
if ( !$db->tableExists( 'page_restrictions' ) ) {
- $this->error( "page_restrictions table does not exist", true );
+ $this->fatalError( "page_restrictions table does not exist" );
}
$start = $db->selectField( 'page', 'MIN(page_id)', false, __METHOD__ );
if ( !$start ) {
- $this->error( "Nothing to do.", true );
+ $this->fatalError( "Nothing to do." );
}
$end = $db->selectField( 'page', 'MAX(page_id)', false, __METHOD__ );
$queryPage = $specialObj;
} else {
$class = get_class( $specialObj );
- $this->error( "$class is not an instance of QueryPage.\n", 1 );
+ $this->fatalError( "$class is not an instance of QueryPage.\n" );
die;
}
// Get the options and update stats
if ( $option ) {
if ( !array_key_exists( $option, $defaultOptions ) ) {
- $this->error( "Invalid user option. Use --list to see valid choices\n", 1 );
+ $this->fatalError( "Invalid user option. Use --list to see valid choices\n" );
}
$userValue = $user->getOption( $option );
}
public function execute() {
$validator = new ExtensionJsonValidator( function ( $msg ) {
- $this->error( $msg, 1 );
+ $this->fatalError( $msg );
} );
$validator->checkDependencies();
$path = $this->getArg( 0 );
$validator->validate( $path );
$this->output( "$path validates against the schema!\n" );
} catch ( ExtensionJsonValidationError $e ) {
- $this->error( $e->getMessage(), 1 );
+ $this->fatalError( $e->getMessage() );
}
}
}
public function execute() {
$title = Title::newFromText( $this->getArg() );
if ( !$title ) {
- $this->error( "Invalid title", true );
+ $this->fatalError( "Invalid title" );
}
$page = WikiPage::factory( $title );
$content = $page->getContent( Revision::RAW );
if ( !$content ) {
- $this->error( "Page has no content", true );
+ $this->fatalError( "Page has no content" );
}
if ( !$content instanceof TextContent ) {
- $this->error( "Non-text content models not supported", true );
+ $this->fatalError( "Non-text content models not supported" );
}
$this->output( $content->getNativeData() );
// Check that type exists and is a layered type
if ( !isset( $typeInfo[$layeredType] ) ) {
- $this->error( 'Undefined password type', true );
+ $this->fatalError( 'Undefined password type' );
}
$passObj = $passwordFactory->newFromType( $layeredType );
if ( !$passObj instanceof LayeredParameterizedPassword ) {
- $this->error( 'Layered parameterized password type must be used.', true );
+ $this->fatalError( 'Layered parameterized password type must be used.' );
}
// Extract the first layer type
],
],
'mediawiki.special' => [
- 'styles' => 'resources/src/mediawiki.special/mediawiki.special.css',
+ 'styles' => [
+ 'resources/src/mediawiki.special/mediawiki.special.css',
+ 'resources/src/mediawiki.special/mediawiki.special.userrights.css',
+ ],
'targets' => [ 'desktop', 'mobile' ],
],
'mediawiki.special.apisandbox.styles' => [
'api-help-param-integer-minmax',
'api-help-param-multi-separate',
'api-help-param-multi-max',
+ 'api-help-param-maxbytes',
+ 'api-help-param-maxchars',
'apisandbox-submit-invalid-fields-title',
'apisandbox-submit-invalid-fields-message',
'apisandbox-results',
'mediawiki.notification.convertmessagebox',
],
],
- 'mediawiki.special.userrights.styles' => [
- 'styles' => 'resources/src/mediawiki.special/mediawiki.special.userrights.css',
- ],
'mediawiki.special.watchlist' => [
'scripts' => 'resources/src/mediawiki.special/mediawiki.special.watchlist.js',
'messages' => [
'oojs-ui.styles.icons-moderation',
],
'messages' => [
+ 'ooui-item-remove',
'ooui-outline-control-move-down',
'ooui-outline-control-move-up',
'ooui-outline-control-remove',
* Reset to default filters
*/
mw.rcfilters.Controller.prototype.resetToDefaults = function () {
- if ( this.applyParamChange( this._getDefaultParams() ) ) {
+ var params = this._getDefaultParams();
+ if ( this.applyParamChange( params ) ) {
// Only update the changes list if there was a change to actual filters
this.updateChangesList();
+ } else {
+ this.uriProcessor.updateURL( params );
}
};
if ( this.applyParamChange( {} ) ) {
// Only update the changes list if there was a change to actual filters
this.updateChangesList();
+ } else {
+ this.uriProcessor.updateURL();
}
if ( highlightedFilterNames ) {
if ( this.applyParamChange( params ) ) {
// Update changes list only if there was a difference in filter selection
this.updateChangesList();
+ } else {
+ this.uriProcessor.updateURL( params );
}
// Log filter grouping
} ) );
}
}
+ if ( 'maxbytes' in pi.parameters[ i ] ) {
+ descriptionContainer.append( $( '<div>', {
+ addClass: 'info',
+ append: Util.parseMsg( 'api-help-param-maxbytes', pi.parameters[ i ].maxbytes )
+ } ) );
+ }
+ if ( 'maxchars' in pi.parameters[ i ] ) {
+ descriptionContainer.append( $( '<div>', {
+ addClass: 'info',
+ append: Util.parseMsg( 'api-help-param-maxchars', pi.parameters[ i ].maxchars )
+ } ) );
+ }
helpField = new OO.ui.FieldLayout(
new OO.ui.Widget( {
$content: '\xa0',
<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.svg" class="image"><img alt="" src="http://example.com/images/thumb/f/ff/Foobar.svg/180px-Foobar.svg.png" width="180" height="135" class="thumbimage" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/270px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/360px-Foobar.svg.png 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.svg" class="internal" title="Enlarge"></a></div>lang=invalid:language:code</div></div></div>
!! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/f/ff/Foobar.svg" data-file-width="240" data-file-height="180" data-file-type="drawing" height="165" width="220"/></a><figcaption>lang=invalid.language.code</figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/thumb/f/ff/Foobar.svg/220px-Foobar.svg.png" data-file-width="240" data-file-height="180" data-file-type="drawing" height="165" width="220"/></a><figcaption>lang=invalid:language:code</figcaption></figure>
!! end
!! test
!! wikitext
<div title="[[Main Page]]"></div>
!! html
-<div title="[[Main Page]]"></div>
+<div title="[[Main Page]]"></div>
!! end
!! wikitext
<div title="[http://example.com/ link]"></div>
!! html
-<div title="[http://example.com/ link]"></div>
+<div title="[http://example.com/ link]"></div>
!! end
-{H|foAjrjvi=>sr-el:" onload="alert(1)" data-foo="}-
[[File:Foobar.jpg|alt=-{}-foAjrjvi-{}-]]
-!! html
+!! html/php
<p>
</p><p><a href="/wiki/%D0%94%D0%B0%D1%82%D0%BE%D1%82%D0%B5%D0%BA%D0%B0:Foobar.jpg" class="image"><img alt="" onload="alert(1)" data-foo="" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
+!! html/parsoid
+<p><meta typeof="mw:LanguageVariant" data-mw-variant='{"add":true,"oneway":[{"f":"foAjrjvi","l":"sr-el","t":"\" onload=\"alert(1)\" data-foo=\""}]}'/></p>
+
+<p><span class="mw-default-size" typeof="mw:Image"><a href="./Датотека:Foobar.jpg"><img alt="foAjrjvi" resource="./Датотека:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"alt":"foAjrjvi","resource":"./Датотека:Foobar.jpg","height":"220","width":"1941"},"sa":{"alt":"alt=-{}-foAjrjvi-{}-","resource":"File:Foobar.jpg"}}'/></a></span></p>
!! end
!! test
<p><a href="#Foo_bar">#Foo bar</a>
</p>
!! end
+
+!! test
+T51672: Test for brackets in attributes of elements in external link texts
+!! wikitext
+[http://example.com/ link <span title="title with [brackets]">span</span>]
+[http://example.com/ link <span title="title with [brackets]">span</span>]
+
+!! html/php
+<p><a rel="nofollow" class="external text" href="http://example.com/">link <span title="title with [brackets]">span</span></a>
+<a rel="nofollow" class="external text" href="http://example.com/">link <span title="title with [brackets]">span</span></a>
+</p>
+!! end
+
+!! test
+T72875: Test for brackets in attributes of elements in internal link texts
+!! wikitext
+[[Foo|link <span title="title with [[double brackets]]">span</span>]]
+[[Foo|link <span title="title with [[double brackets]]">span</span>]]
+
+!! html/php
+<p><a href="/wiki/Foo" title="Foo">link <span title="title with [[double brackets]]">span</span></a>
+<a href="/wiki/Foo" title="Foo">link <span title="title with [[double brackets]]">span</span></a>
+</p>
+!! end
+
+!! test
+T179544: {{anchorencode:}} output should be always usable in links
+!! config
+wgFragmentMode=[ 'html5' ]
+!! wikitext
+<span id="{{anchorencode:[foo]}}"></span>[[#{{anchorencode:[foo]}}]]
+!! html/php
+<p><span id="[foo]"></span><a href="#[foo]">#[foo]</a>
+</p>
+!! end
+
+## ------------------------------
+## Parsoid section-wrapping tests
+## ------------------------------
+!! test
+Section wrapping for well-nested sections (no leading content)
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+= 1 =
+a
+
+= 2 =
+b
+
+== 2.1 ==
+c
+
+== 2.2 ==
+d
+
+=== 2.2.1 ===
+e
+
+= 3 =
+f
+!! html/parsoid
+<section data-mw-section-id="1"><h1 id="1"> 1 </h1>
+<p>a</p>
+
+</section><section data-mw-section-id="2"><h1 id="2"> 2 </h1>
+<p>b</p>
+
+<section data-mw-section-id="3"><h2 id="2.1"> 2.1 </h2>
+<p>c</p>
+
+</section><section data-mw-section-id="4"><h2 id="2.2"> 2.2 </h2>
+<p>d</p>
+
+<section data-mw-section-id="5"><h3 id="2.2.1"> 2.2.1 </h3>
+<p>e</p>
+
+</section></section></section><section data-mw-section-id="6"><h1 id="3"> 3 </h1>
+<p>f</p>
+
+</section>
+!! end
+
+!! test
+Section wrapping for well-nested sections (with leading content)
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+Para 1.
+
+Para 2 with a <div>nested in it</div>
+
+Para 3.
+
+= 1 =
+a
+
+= 2 =
+b
+
+== 2.1 ==
+c
+!! html/parsoid
+<section data-mw-section-id="0"><p>Para 1.</p>
+
+<p>Para 2 with a </p><div>nested in it</div>
+
+<p>Para 3.</p>
+
+</section><section data-mw-section-id="1"><h1 id="1"> 1 </h1>
+<p>a</p>
+
+</section><section data-mw-section-id="2"><h1 id="2"> 2 </h1>
+<p>b</p>
+
+<section data-mw-section-id="3"><h2 id="2.1"> 2.1 </h2>
+<p>c</p>
+
+</section></section>
+!! end
+
+!! test
+Section wrapping with template-generated sections (good nesting 1)
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+= 1 =
+a
+
+{{echo|1=
+== 1.1 ==
+b
+}}
+
+== 1.2 ==
+c
+
+= 2 =
+d
+!! html/parsoid
+<section data-mw-section-id="1"><h1 id="1"> 1 </h1>
+<p>a</p>
+
+<section data-mw-section-id="-1"><h2 about="#mwt1" typeof="mw:Transclusion" id="1.1" data-parsoid='{"dsr":[9,33,null,null],"pi":[[{"k":"1","named":true,"spc":["","","\n","\n"]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"== 1.1 ==\nb"}},"i":0}}]}'> 1.1 </h2><span about="#mwt1">
+</span><p about="#mwt1">b</p>
+</section><section data-mw-section-id="3"><h2 id="1.2"> 1.2 </h2>
+<p>c</p>
+
+</section></section><section data-mw-section-id="4"><h1 id="2"> 2 </h1>
+<p>d</p></section>
+!! end
+
+# In this example, the template scope is mildly expanded to incorporate the
+# trailing newline after the transclusion since that is part of section 1.1.1
+!! test
+Section wrapping with template-generated sections (good nesting 2)
+!! options
+parsoid={
+ "wrapSections": true,
+ "modes": ["wt2html", "wt2wt"]
+}
+!! wikitext
+= 1 =
+a
+
+{{echo|1=
+== 1.1 ==
+b
+=== 1.1.1 ===
+d
+}}
+= 2 =
+e
+!! html/parsoid
+<section data-mw-section-id="1"><h1 id="1"> 1 </h1>
+<p>a</p>
+
+<section data-mw-section-id="-1"><h2 about="#mwt1" typeof="mw:Transclusion" id="1.1" data-parsoid='{"dsr":[9,50,null,null],"pi":[[{"k":"1","named":true,"spc":["","","\n","\n"]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"== 1.1 ==\nb\n=== 1.1.1 ===\nd"}},"i":0}},"\n"]}'> 1.1 </h2><span about="#mwt1">
+</span><p about="#mwt1">b</p><span about="#mwt1">
+</span><section data-mw-section-id="-1" about="#mwt1"><h3 about="#mwt1" id="1.1.1"> 1.1.1 </h3><span about="#mwt1">
+</span><p about="#mwt1">d</p><span about="#mwt1">
+</span></section></section></section><section data-mw-section-id="4" data-parsoid="{}"><h1 id="2"> 2 </h1>
+<p>e</p></section>
+!! end
+
+# In this example, the template scope is mildly expanded to incorporate the
+# trailing newline after the transclusion since that is part of section 1.2.1
+!! test
+Section wrapping with template-generated sections (good nesting 3)
+!! options
+parsoid={
+ "wrapSections": true,
+ "modes": ["wt2html", "wt2wt"]
+}
+!! wikitext
+= 1 =
+a
+
+{{echo|1=
+x
+== 1.1 ==
+b
+==1.2==
+c
+===1.2.1===
+d
+}}
+= 2 =
+e
+!! html/parsoid
+<section data-mw-section-id="1" data-parsoid="{}"><h1 id="1"> 1 </h1>
+<p>a</p>
+
+<p about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"dsr":[9,60,0,0],"pi":[[{"k":"1","named":true,"spc":["","","\n","\n"]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"x\n== 1.1 ==\nb\n==1.2==\nc\n===1.2.1===\nd"}},"i":0}},"\n"]}'>x</p><span about="#mwt1">
+</span><section data-mw-section-id="-1" about="#mwt1"><h2 about="#mwt1" id="1.1"> 1.1 </h2><span about="#mwt1">
+</span><p about="#mwt1">b</p><span about="#mwt1">
+</span></section><section data-mw-section-id="-1" about="#mwt1"><h2 about="#mwt1" id="1.2">1.2</h2><span about="#mwt1">
+</span><p about="#mwt1">c</p><span about="#mwt1">
+</span><section data-mw-section-id="-1" about="#mwt1"><h3 about="#mwt1" id="1.2.1">1.2.1</h3><span about="#mwt1">
+</span><p about="#mwt1">d</p><span about="#mwt1">
+</span></section></section></section><section data-mw-section-id="5"><h1 id="2"> 2 </h1>
+<p>e</p></section>
+!! end
+
+# Because of section-wrapping and template-wrapping interactions,
+# the scope of the template is expanded so that the template markup
+# is valid in the presence of <section> tags.
+!! test
+Section wrapping with template-generated sections (bad nesting 1)
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+= 1 =
+a
+
+{{echo|1=
+= 2 =
+b
+== 2.1 ==
+c
+}}
+
+d
+
+= 3 =
+e
+!! html/parsoid
+<section data-mw-section-id="1"><h1 id="1"> 1 </h1>
+<p>a</p>
+
+</section><section data-mw-section-id="-1"><h1 about="#mwt1" typeof="mw:Transclusion" id="2" data-parsoid='{"dsr":[9,45,null,null],"pi":[[{"k":"1","named":true,"spc":["","","\n","\n"]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"= 2 =\nb\n== 2.1 ==\nc"}},"i":0}},"\n\nd\n\n"]}'> 2 </h1><span about="#mwt1">
+</span><p about="#mwt1">b</p><span about="#mwt1">
+</span><section data-mw-section-id="-1" about="#mwt1"><h2 about="#mwt1" id="2.1"> 2.1 </h2><span about="#mwt1">
+</span><p about="#mwt1">c</p><span about="#mwt1">
+
+</span><p about="#mwt1">d</p><span about="#mwt1">
+
+</span></section></section><section data-mw-section-id="4"><h1 id="3"> 3 </h1>
+<p>e</p></section>
+!! end
+
+# Because of section-wrapping and template-wrapping interactions,
+# additional template wrappers are added to <section> tags
+# so that template wrapping semantics are valid whether section
+# tags are retained or stripped. But, the template scope can expand
+# greatly when accounting for section tags.
+!! test
+Section wrapping with template-generated sections (bad nesting 2)
+!! options
+parsoid={
+ "wrapSections": true,
+ "modes": ["wt2html", "wt2wt"]
+}
+!! wikitext
+= 1 =
+a
+
+{{echo|1=
+== 1.2 ==
+b
+= 2 =
+c
+}}
+
+d
+
+= 3 =
+e
+!! html/parsoid
+<section data-mw-section-id="1" about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":["= 1 =\na\n\n",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"== 1.2 ==\nb\n= 2 =\nc"}},"i":0}},"\n\nd\n\n"]}'><h1 id="1"> 1 </h1>
+<p>a</p>
+
+<section data-mw-section-id="-1"><h2 about="#mwt1" typeof="mw:Transclusion" id="1.2" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"== 1.2 ==\nb\n= 2 =\nc"}},"i":0}}]}'> 1.2 </h2><span about="#mwt1">
+</span><p about="#mwt1">b</p><span about="#mwt1">
+</span></section></section><section data-mw-section-id="-1" about="#mwt1"><h1 about="#mwt1" id="2"> 2 </h1><span about="#mwt1">
+</span><p about="#mwt1">c</p>
+
+<p>d</p>
+</section><section data-mw-section-id="4" data-parsoid="{}"><h1 id="3"> 3 </h1>
+<p>e</p></section>
+!! end
+
+!! test
+Section wrapping with uneditable lead section + div wrapping multiple sections
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+foo
+
+<div style="border:1px solid red;">
+= 1 =
+a
+
+== 1.1 ==
+b
+
+= 2 =
+c
+</div>
+
+= 3 =
+d
+
+== 3.1 ==
+e
+!! html/parsoid
+<section data-mw-section-id="-1"><p>foo</p>
+
+</section><section data-mw-section-id="-2"><div style="border:1px solid red;">
+<section data-mw-section-id="1"><h1 id="1"> 1 </h1>
+<p>a</p>
+
+<section data-mw-section-id="2"><h2 id="1.1"> 1.1 </h2>
+<p>b</p>
+
+</section></section><section data-mw-section-id="-1"><h1 id="2"> 2 </h1>
+<p>c</p>
+</section></div>
+
+</section><section data-mw-section-id="4"><h1 id="3"> 3 </h1>
+<p>d</p>
+
+<section data-mw-section-id="5"><h2 id="3.1"> 3.1 </h2>
+<p>e</p>
+</section></section>
+!! end
+
+!! test
+Section wrapping with editable lead section + div overlapping multiple sections
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+foo
+
+= 1 =
+a
+<div style="border:1px solid red;">
+b
+
+== 1.1 ==
+c
+
+= 2 =
+d
+</div>
+e
+
+= 3 =
+f
+
+== 3.1 ==
+g
+!! html/parsoid
+<section data-mw-section-id="0"><p>foo</p>
+
+</section><section data-mw-section-id="-1"><h1 id="1"> 1 </h1>
+<p>a</p>
+</section><section data-mw-section-id="-2"><div style="border:1px solid red;">
+<p>b</p>
+
+<section data-mw-section-id="2"><h2 id="1.1"> 1.1 </h2>
+<p>c</p>
+
+</section><section data-mw-section-id="-1"><h1 id="2"> 2 </h1>
+<p>d</p>
+</section></div>
+<p>e</p>
+
+</section><section data-mw-section-id="4"><h1 id="3"> 3 </h1>
+<p>f</p>
+
+<section data-mw-section-id="5"><h2 id="3.1"> 3.1 </h2>
+<p>g</p>
+</section></section>
+!! end
+
+!! test
+HTML header tags should not be wrapped in section tags
+!! options
+parsoid={
+ "wrapSections": true
+}
+!! wikitext
+foo
+
+<h1>a</h1>
+
+= b =
+
+<h1>c</h1>
+
+= d =
+!! html/parsoid
+<section data-mw-section-id="0"><p>foo</p>
+
+<h1 id="a" data-parsoid='{"stx":"html"}'>a</h1>
+
+</section><section data-mw-section-id="1"><h1 id="b"> b </h1>
+
+<h1 id="c" data-parsoid='{"stx":"html"}'>c</h1>
+
+</section><section data-mw-section-id="2"><h1 id="d"> d </h1></section>
+!! end
);
}
+ /**
+ * @covers Html::warningBox
+ * @covers Html::messageBox
+ */
+ public function testWarningBox() {
+ $this->assertEquals(
+ Html::warningBox( 'warn' ),
+ '<div class="warningbox">warn</div>'
+ );
+ }
+
+ /**
+ * @covers Html::errorBox
+ * @covers Html::messageBox
+ */
+ public function testErrorBox() {
+ $this->assertEquals(
+ Html::errorBox( 'err' ),
+ '<div class="errorbox">err</div>'
+ );
+ $this->assertEquals(
+ Html::errorBox( 'err', 'heading' ),
+ '<div class="errorbox"><h2>heading</h2>err</div>'
+ );
+ }
+
+ /**
+ * @covers Html::successBox
+ * @covers Html::messageBox
+ */
+ public function testSuccessBox() {
+ $this->assertEquals(
+ Html::successBox( 'great' ),
+ '<div class="successbox">great</div>'
+ );
+ $this->assertEquals(
+ Html::successBox( '<script>beware no escaping!</script>' ),
+ '<div class="successbox"><script>beware no escaping!</script></div>'
+ );
+ }
+
/**
* List of input element types values introduced by HTML5
* Full list at https://www.w3.org/TR/html-markup/input.html
<?php
+use Wikimedia\TestingAccessWrapper;
+
/**
* @covers DifferenceEngine
*
$this->assertEquals( $revs[2], $diffEngine->getNewid(), 'diff get new id' );
}
+ public function provideLocaliseTitleTooltipsTestData() {
+ return [
+ 'moved paragraph left shoud get new location title' => [
+ '<a class="mw-diff-movedpara-left">⚫</a>',
+ '<a class="mw-diff-movedpara-left" title="(diff-paragraph-moved-tonew)">⚫</a>',
+ ],
+ 'moved paragraph right shoud get old location title' => [
+ '<a class="mw-diff-movedpara-right">⚫</a>',
+ '<a class="mw-diff-movedpara-right" title="(diff-paragraph-moved-toold)">⚫</a>',
+ ],
+ 'nothing changed when key not hit' => [
+ '<a class="mw-diff-movedpara-rightis">⚫</a>',
+ '<a class="mw-diff-movedpara-rightis">⚫</a>',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideLocaliseTitleTooltipsTestData
+ */
+ public function testAddLocalisedTitleTooltips( $input, $expected ) {
+ $this->setContentLang( 'qqx' );
+ $diffEngine = TestingAccessWrapper::newFromObject( new DifferenceEngine() );
+ $this->assertEquals( $expected, $diffEngine->addLocalisedTitleTooltips( $input ) );
+ }
+
}
);
$this->assertEquals(
- 'test:##dc89dcb43b28614da27660240af478b5',
+ 'test:BagOStuff-long-key:##dc89dcb43b28614da27660240af478b5',
$this->cache->makeKey( '𝕖𝕧𝕖𝕟', '𝕚𝕗', '𝕨𝕖', '𝕄𝔻𝟝', '𝕖𝕒𝕔𝕙',
'𝕒𝕣𝕘𝕦𝕞𝕖𝕟𝕥', '𝕥𝕙𝕚𝕤', '𝕜𝕖𝕪', '𝕨𝕠𝕦𝕝𝕕', '𝕤𝕥𝕚𝕝𝕝', '𝕓𝕖', '𝕥𝕠𝕠', '𝕝𝕠𝕟𝕘' )
);
foreach ( [ $paramsPlain, $paramsForHelp ] as $params ) {
foreach ( $params as $param => $config ) {
- if (
- isset( $config[ApiBase::PARAM_ISMULTI_LIMIT1] )
+ if ( isset( $config[ApiBase::PARAM_ISMULTI_LIMIT1] )
|| isset( $config[ApiBase::PARAM_ISMULTI_LIMIT2] )
) {
$this->assertTrue( !empty( $config[ApiBase::PARAM_ISMULTI] ), $param
$config[ApiBase::PARAM_ISMULTI_LIMIT2], $param
. 'PARAM_ISMULTI limit cannot be smaller for users with apihighlimits rights' );
}
+ if ( isset( $config[ApiBase::PARAM_MAX_BYTES] )
+ || isset( $config[ApiBase::PARAM_MAX_CHARS] )
+ ) {
+ $default = isset( $config[ApiBase::PARAM_DFLT] ) ? $config[ApiBase::PARAM_DFLT] : null;
+ $type = isset( $config[ApiBase::PARAM_TYPE] ) ? $config[ApiBase::PARAM_TYPE]
+ : gettype( $default );
+ $this->assertContains( $type, [ 'NULL', 'string', 'text', 'password' ],
+ 'PARAM_MAX_BYTES/CHARS is only supported for string-like types' );
+ }
}
}
}